Hey, is there any plan to add FP programming (functors, mondads, free monad, etc.) to Rust as part of the language or some officialy supported library?
Also - any plans for Streaming?
I'm a big Scala user and fan (Haskell of course too) and would like to try out Rust but I really don't see myself going back to structural/object oriented-programming
You can write Rust in a quite functional matter, but as a language it works quite differently from Haskell or Scala. There's been a lot of discussion [1] around extending the type system to support something akin to higher-kinded types. The current result is the RFC you can find in [2], as well as a work-in-progress implementation of a new trait-resolution system for use in the compiler [3].
Can you link to some documentation about how monads work in rust? Based on the sibling comment rust doesn't support higher-kinded types yet, so I'm interested in how you would encode this in rust.
How does your implementation of associated types differ from the same feature in Haskell? In particular, why is this related to higher-kinded types?
From what I remember from the theory, higher-kinded types lead to a genuinely more difficult type inference problem (going from easy first-order unification to undecidable higher-order unification), while associated types are simply existential types (and in particular no more difficult than higher-rank polymorphism).
Ok, I did, so here's the answer in case anybody else is confused:
The feature under discussion is "associated type constructors". Rust already has associated types in traits (I didn't know that part and was confused), and what this feature adds is that it allows us to associate a first-order type constructor to a trait.
Since the type constructor is first-order, and first-order type constructors are already present in the base language in the form of generic types, the implementation is simplified to the point that it can reuse the existing infrarstructure for type inference.
---
Apart from that, the reason for this implementation choice seems to be that it's required for precise lifetime management. Almost all datastructures in rust seem to be parameterized with a lifetime argument, even if they have no further type parameters. Since there is no such thing as a second-order lifetime (i.e., a "lifetime constructor" T : (lifetime -> lifetime) -> lifetime), first-order type constructors are enough to handle all issues that pop up because of lifetime management.
---
That actually seems like a very pragmatic design. The only problem I had while reading this RFC is that the combination of "multiple-inheritance" in traits with their built in namespacing leads to some really ugly syntax, e.g., "<T as Foo>::Bar<'a, 'static>;". Is this already idiomatic rust?
There's several more things that confuse me in this RFC, but this is probably the wrong place to discuss these things. Incidentally, what is the right place to talk about this?
"zero-cost abstractions" means "zero extra cost". Everything has a cost. The borrow checker has zero runtime cost though, as it's entirely at compile time.
I meant to suggest that opt-in features for FP style programming might be accepted, since there doesn't seem to be an actual ban on non-zero-cost abstractions.
Re "extra", I'm not sure what you mean - does it mean that the abstraction is implemented efficiently, but still has the cost that is usually associated with that abstraction? For example, dynamic dispatch is as fast as a DIY dynamic dispatch in assembly?
The borrow checker, AFAIU, has a cost in that it forbids some (more efficient) shared data accesses that it can't prove safe but still are.
> For example, dynamic dispatch is as fast as a DIY dynamic dispatch in assembly?
Yes, zero cost means you could not implement it in a more efficient way by hand.
> The borrow checker, AFAIU, has a cost in that it forbids some (more efficient) shared data accesses that it can't prove safe but still are.
This is not what it meant with "cost" in this context.
It is zero cost because it only runs at compile time. That it rejects some valid programs is irrelevant, because it is zero cost for the programs it accepts.
Interesting. But I think restricively molding what can be compiled can also be an abstraction cost. Maybe it's more one of those "you know it when you see it" things?
Rust's bound checks are cited a lot here on hn as a performance concern but in practice I rarely encounter them because in rust you mostly use the iterator API for loops and it doesn't need bound checking.
For an illustration of a safety-related runtime cost that you almost always have to pay, Uft8 validation of strings is a better example IMHO.
Good question! It was merged quite close to the release, so it's hard to guess if it landed in beta already. So, let's have a look at [1]! And yep, it's in the beta branch.
This means it's in 1.23, which'll be released in 6 weeks.
Yes, once [1] lands, you can add it with `rustup target add wasm32-unknown-unknown` and then you can `cargo build --target=wasm32-unknown-unknown`.
You should note that it's not as simple as that, though. Depending on the context you want to run WASM you might need to do additional steps. E.g., when running this in a browser, you might not be able to use the std lib to spawn threads just yet.
I didn't realise it already worked in other contexts.
Mistakes in terms of understanding the code and writing what you think you're writing. I've only just begun learning Rust, and one of the most difficult things has been figuring out what types my variables have when everything is implicit and based on type inference. Variables that act both as pointers and non-pointers seem pretty confusing. It's like temporarily and invisibly turning off parts of the type-checker.
ETA: To me it feels a bit like the icky type coercion magic some languages have that lets you write code that kind of works even though you don't understand what you're doing. I don't know Rust though, so this may be totally different.
If you happen to be coming from C, keep in mind that integers in Rust don't coerce into anything else like they do in C; e.g. adding an 8-bit integer to a 32-bit integer (without an explicit cast) is a type error, `if 0` does not work, arithmetic ops on references never do pointer arithmetic, etc.
> ETA: To me it feels a bit like the icky type coercion magic some languages have that lets you write code that kind of works even though you don't understand what you're doing. I don't know Rust though, so this may be totally different.
It is quite different.
In Rust already it is possible to proxy "pointer to a thing" as "the thing itself" in a lot of different places, making it a lot more pleasant to work with. This is a continuation of that trend.
It's pretty easy to know if most things are a pointer or not, but more importantly if it's not immediately obvious it rarely matters (i.e. you're not the one creating or consuming it; you're just sharing borrows to it). At which point sharing pointer-to-value vs pointer-to-pointer isn't very different, and Rust implicitly deals with that.
Ultimately it boils down to the fact that there's basically no interesting difference between an immutable reference to a primitive and the primitive itself in Rust. It can't dangle. The immutability can't be casted away. They just have a different ABI if the references don't get completely optimized away. You could do pointer equality checks on them if you really want?
Swift has taken the observation further and is investigating ways to avoid ever having a immutable+shared-reference-to-primitive vs primitive distinction in the language, while still introducing this distinction for types where it is interesting. (e.g. reference counted classes or atomics)
Okay, so I came from C and this part really put me off. Why? Because I like saying what I mean, and meaning what I say. Nothing hidden, nothing implicit. And C, as opposed to C++, does not allow you to say something you don't mean.
Here is one C++ example, without looking for the signature of f(), there's no way to tell the answer. So essentially two pieces of identical code with identical input values and no external side effects can still give you different output. Ah C++, the garbage that you are.
int x = 5;
f(x);
// WHAT IS THE VALUE OF X HERE?
int x = 5;
f(x);
// WHAT IS THE VALUE OF X HERE?
So why is Rust any better in this regard? Because at the end of the day, Rust deals with actual concrete types and values, the only thing references are good for is being references: they sometimes save you some copying and let you refer to stuff and that's pretty much it. So the "ref1 == ref2" operation is a deep equality check and not a "does ref1 point to the same object as ref2" check. Because sometimes objects in different places in memory can be semantically equal. So it's okay for obj1 to mean obj1, &obj1 to mean obj1, &&obj1 to mean obj1, etc...
If you need raw pointers like you need in C, because you know the objects you want to compare are singletons, then you can always cast using
&obj as *const Obj
or
&mut obj as *mut Obj
But that's an escape hatch.
BTW, this auto-dereferencing you're experiencing is part of the Deref trait if you ever want to overload it. But that's, in my own opinion, an escape hatch as well.
All in all, unless you're doing very specific things, try to write your code in high-level semantics (meaning using these deep-equality rather than pointer-equality semantics), and then benchmark and find out which parts are hurting your performance. Rust allows you to do that and still get between very-reasonable-and-very-good speed.
>Okay, so I came from C and this part really put me off. Why? Because I like saying what I mean, and meaning what I say. Nothing hidden, nothing implicit.
Are we talking about the same language here? The language where arrays implicitly decay to pointers, where integer types get implicitly promoted all over the place, where aliasing rules implicitly define which pointers can and cannot alias, where partial initialization of a struct or array implicitly sets the other members to 0? Where the language will let you call an undeclared function and make up a prototype on the fly for you?
I also don't understand your C++ example, without side-effect why would both invocations of f() within the same scope produce a different result? I thought you wanted to criticise function overloading but you call it with an int both times so I don't see what's you're getting at.
Or maybe you meant that the two calls are in a different scope and could resolve to a different function? But you can do that in C as well in two different translation units and making two static f() implementations.
> Are we talking about the same language here? The language where arrays implicitly decay to pointers, where integer types get implicitly promoted all over the place, where aliasing rules implicitly define which pointers can and cannot alias, where partial initialization of a struct or array implicitly sets the other members to 0? Where the language will let you call an undeclared function and make up a prototype on the fly for you?
-Wall -Wextra and those issues are made clear to you. But granted, that's part of a good compiler and not part of the language.
> I also don't understand your C++ example, without side-effect why would both invocations of f() within the same scope produce a different result? I thought you wanted to criticise function overloading but you call it with an int both times so I don't see what's you're getting at.
I'm not criticizing function overloading.
> Or maybe you meant that the two calls are in a different scope and could resolve to a different function? But you can do that in C as well in two different translation units and making two static f() implementations.
Yes the two functions are different, but not necessarily in scope, they don't necessarily need to have the same names. Yet at the calling site they look exactly the same: one will modify your data without you being aware of it and the other will not, and there is no syntactic hint to differentiate them.
if you really want to make sure that x is not changed by f, declare it const. Sure, f can cast the constness away, but then again it could also walk up the stack in C and trash your stack frame anyway. It is UB in either case.
The typesystem in C++ can help protect against Murphy, but (unfortunately) not Machiavelli.
Agreed. But my example isn't about security or UB. It is about the language allowing you to write two IDENTICAL snippets of code that end up meaning two different things without any overloading involved. Like saying something and meaning something else.
In C, the value of x in that fragment is still the same: a pointer (ok, not exactly, but something which behaves like a pointer) to a memory location where an int can be stored. The f(x) call can't change that pointer.
It is exactly the same between the two cases: the x object (either the array memory location or the integer memory location) can be changed by 'f' because mentioning the 'x' object name in a function call may implicitly converts to the address of the object, i.e. it passes by reference).
Considering pointer arithmetic is unsafe, there are no other interpretations, I think? I can’t think of an example where this would mask a mistake in Rust.
As for confusion, both are accepted. I imagine clippy will eventually have advice on this situation. For example, clippy also warns you about taking a reference that the compiler immediatly dereferences.
I’m not sure what the long term plans are for clippy, but personally hope it’ll eventually be warnings in the official compiler.
I just wanted to point out that this is largely a misconception. Rust, or the Rust creators, do not consider pointer arithmetic to be fundamentally `unsafe`, and the only reason `.offset()` is `unsafe` to begin with is due to optimization concerns[0]. `.wrapping_offset()` exists and is marked safe, despite achieving the exactly same thing as `.offset()` for the majority of scenarios[1], and more-over you can cast any pointer to an integer using only safe code, at which point you can perform all of your arithmetic on the integer and then cast it back also using only safe code[2]. The bottom line is that things like pointer arithmetic are generally not considered unsafe because the operation has defined semantics for what happens. It is only the point where you attempt to use the value, the dereference, that is `unsafe`, as that may blow-up if the value is an invalid memory location.
I mean, that's largely up to you. For Rust, generally probably not, but I would wager there are situations where it might be useful to add some offsets and compare various pointer values without actually ever dereferencing them.
My point was more that there is a difference between what people consider to be "bug-likely" operations, and what Rust considers an `unsafe` operation, and because of this I think people are too quick to think the `unsafe` system will always save them and make it easy to verify their code. If you do tons of pointer arithmetic and then do one dereference at the end, you're going to only have one line of `unsafe` code, which is "good". But if that one line blows up, then the actual bug is somewhere in your safe code, not the one line of `unsafe` code, so judging safety based only on how much `unsafe` code is really not a great way to do things.
And I don't say this as a theory about what people are thinking. This paper[0] got passed around a bit a while back (Most only Reddit, I can't seem to find a HN page that got very popular), and they quite literally create an `Address` object that exposes a safe `plus` function (for pointer arithmetic) and an `unsafe` `load` function for dereferencing. And then they just do a pretty much straight conversion of the C code, replacing all the pointers with `Address` objects, replacing all of the pointer arithmetic with `plus`s, and replacing all of the dereferences with `load`s. And then they claim it is tons safer then the C code because it uses so little `unsafe` code, despite the fact that it can easily have the exact same bugs the C code can have if there are any bugs in their pointer arithmetic. So, in this situation, the amount of `unsafe` code really doesn't actually matter because it doesn't really mean anything about the number of bugs in the program. All it indicates is that "these are the spots where the program could blow-up", which you could probably already figure out fairly easy from looking at C code.
And don't get the wrong idea, I do still like Rust and I think it has some great features in it (And some misfeatures, but that's true of every language). But, at the very least, I think the `unsafe` system leaves a lot to be desired and things like the tutorials give the wrong impression about what `unsafe` actually indicates.
The idea behind unsafe is that you're not allowed to do that sort of thing.
There's obviously not a way to enforce that in the compiler, but that's because unsafe is inherently a way to extend what the compiler can enforce. It's like you're adding code to the compiler.
Assuming that's done correctly (as e.g. the stdlib is in the process of being proven to do) then any client safe code is truly known not to have any memory safety problems.
Would you mind elaborating on what you're trying to get at with your first two sentences? What "sort of thing" are you talking about?
Also, define 'correct'. What does it mean for a `unsafe` block to be 'correct'?
My point is largely that for an `unsafe` block to be correct, it relies on other safe code to also be correct in ways the compiler can't verify. So just because your `unsafe` blocks are small and infrequent doesn't say anything about the quality or "buggyness" of your code as a whole if those `unsafe` blocks are dependent on largely the entire rest of the safe code.
> just because your `unsafe` blocks are small and infrequent doesn't say anything about the quality or "buggyness" of your code as a whole if those `unsafe` blocks are dependent on largely the entire rest of the safe code.
This is true; what I'm getting at is that you shouldn't allow your unsafe code to depend on "largely the entire rest of the safe code."
Unsafe code should be used together with the visibility system, the borrow checker, and the trait system, so that it only depends on a very small amount of code within the same file or even the same function.
Then the vast majority of the program can be written in safe code that cannot violate the invariants of the unsafe code.
Sorry for not responding sooner, but I agree with you completely that the right thing to do is not to focus on "how many" `unsafe` lines you have, but how well encapsulated they are into a module that can't be broken by outside input. And I'm glad we both see this. My "problem" is less that `unsafe` doesn't do the things I mentioned, but that whenever Rust and `unsafe` come-up there tends to be lots of people who don't understand what it is, or how it works, and I think that is a very dangerous mindset because it leads to completely pointless (and dangerous, from a safety perspective) stuff like how that paper literally measured how many lines were `unsafe` and displayed it as a percentage to prove their code was good.
With all that said, while this is a separate topic, I think that the complete lack of a spec for lots of details surrounding `unsafe` makes it still basically impossible to do correctly. I think Rust really needs some type of language spec, but it doesn't look like any work is being done on that front [0].
I agree. Some of the things done in the name of ergonomics just strike me as wrong and/or possibly misleading for learners.
People who say Rust is hard to learn have always confused me. I learned programming in this order: scheme, C, C++, Haskell, Rust. I believe that if you go in another order it may make things more difficult, but some of these difficulties are inherent. To me, a reference to a thing is inherently different from the thing, and a systems language should make that clear. I didn't even like auto-deref, I felt like there should be an operator similar to
thing->method()
in C++ that can be chained so that references and indirection would always be syntactically clear and distinct, but the ergonomics on that won out.
The change in question wasn't done because of ergonomics, it was done for consistency with the rest of the language. Read about it in the originating PR (which was not linked directly from the blog post): https://github.com/rust-lang/rust/pull/41336
Folks always say this but never really clarify why this should be the case.
When doing low lever unsafe stuff Rust does indeed make it very clear if something is a pointer or not, and avoids weird coercions (even C doesn't do well here!)
But for normal code, does it really matter if something is a pointer or not? In C++ already with by-ref and move values and stuff you have implicit pointers in many places that don't behave as pointers.
Rust never goes the way of adding implicit allocations or implicit indirection; it only goes in the way of stripping it, which is rarely if ever a problem except when dealing with unsafe code (where Rust is more explicit on pointers anyway).
"People who say Rust is hard to learn have always confused me. I learned programming in this order: (...)"
(proceeds to list a quite rare and outlier-ish learning sequence, and all in non-scripting languages to boot).
Well, of course those people confused you. What you, who learned Scheme, C++ and Haskell before going to Rust, have in common with someone who only knows C or Java or only got his start in some scripting languages, so as to be able to relate with their "Rust is hard" experience?
I personally went from Go (slightly mingling with it) -> Rust 1.0 (wrote my first applications with it) and found it to be incredibly easy to learn in comparison to C, C++, Java, and even Python and JavaScript. Everything's pretty explicit and straightforward, and the API documentation and associated resources are stellar.
It's a shortcut for "early-returning" in error cases
> Was more boilerplate necessary in the example before?
Somewhat yes, the original use case is Result types (and the try! macro):
let v = func()?;
expands to
let v = match func() {
Ok(v) => v,
Err(e) => return Err(From::from(e))
};
that is it "unwraps" a successful result, and barring one it directly returns from the current function (having possibly converted the error). Before 1.22, doing that with an Option would be:
let v = match func() {
Some(v) => v,
None => return None
};
or
let v = if let Some(v) { v } else { return None };
after 1.22, this becomes
let v = func()?;
as well.
edit: note that this is quite different from the behaviour of the ? operator in languages like C# or Swift where it compounds into "null-safe" operators, the most famous being the null-safe navigation operator "?."[0], these are closer to the monadic bind, which in Rust is called "and_then".
..which seems to be a different style of control flow to choose from. On one hand, I suppose one may avoid using and_then with code dealing with side effects. On another hand, ? seems restricted to being used inside functions that have to return a a single type (like Result) they operate on.
Well, all functions have to return a single type, though that type may be a composite type, like a tuple.
Less pedantically, `?` lets you (well, will let you) unwrap values and convert their error cases between each other. So once the next round of stabilization happens, if you have a function that returns Results, you can mix ? on Options and Results in the body, and vice versa. And it can be extended for other types too. Basically, it's an early return "protocol" if you will.
By single type, I meant to hint not being able to mix ? on Options and Results in the body, but good to know that's soon possible! Though you still can't, eg, use `?` in main()
Also regarding an "early return protocol", I'd also be curious for continue, break, any ! type expression. For one of the Rust projects I worked on, I wrote an unwrap macro on Option that takes a secondary ! argument.
Interesting. Looking at the proposal it seems specific to main() and not any void returning function, by automatically picking an exit status. I suppose I worry the construct has a lack of control (log diagnostics, exit status, cleaning, etc) when bailing and can’t handle eg continue, break too.
exit status and cleaning should be just fine, logging or something else would need to not do it, yeah. continue and break would be weird outside of loops.
Your example is confusing, too; you've removed the code after the try? so it's not equivalent to the example in the blog, and because it's a single thing there's no early return anymore, thoroughly obfuscating what it is that try did :|
Unless I'm missing something it's exactly equivalent (and likely to compile to the same code), after all both check whether there is a value to extract and immediately return None if there isn't, both return Some(val) if there is a value to extract.
If you added extra code to manipulate things then that single if would still be enough, just place everything that happens after the extraction inside the success branch of the if statement.
You are missing something, it won't compile at all.
You're missing a Some(val) at the end. Now you could remove the `if let val` as well to make it compile, but then that obfuscates what the ? operator was doing there.
The error seems to be that `if let val = Some(1)` should be `if let Some(val) = Some(1)` (i.e. the pattern match should have extracted val, which is what the ? operator does as well).
I think it would have been much clearer to have 1 function which took a parameter of type Option<u8>, then did something with it after extracting with ?, then call it twice, once with Some(1), once with None. In addition I'd probably do something with val (e.g. add 1). And to prevent easy conversion to the if statement you could make the early return explicit by doing the extraction somewhere in a loop (so that you can't get at it with nested if statements).
No, that, _and_ that if let ... {} doesn't evaluate to anything; your function doesn't evaluate to anything even though it has a stated return type of Option.
I mean, this isn't hard to check, try compiling it.
I was saying it's misleading not only because it's wrong, but also because even if something is equivalent if you're trying to show how a feature works you want equivalent code that cleanly maps to the original; and isn't further reduced.
Technically it also applies a conversion operator to the "error" value (a no-op for the Option -> Option case, but it can be otherwise for Option -> Result or Result<T, E> -> Result<T, F>
It's used for shortcut error handling: when for instance you receive an error result from opening a file, you usually want to also return with an error. It was used so often that the ? operator was introduced as a shortcut. Now the operator is being extended to Option<T>, which works similar to Result<T, ()>.
Ok so I guess it's a trinary operator with two arguments combined that is able to use Option and Result for the test... I hope autoformatters keep stuff like that on one line!
It's an error handling operator that does an early exit in case of None and unwraps the value in case of Some. It used to work only with Result: early exit when Err, unwrapping the value when Ok.
Does anyone know why the last two lines in main() fail? AFAICS, v has the same type as both functions.
fn try_option_some() -> Option<u8> {
let val = Some(1)?;
Some(val)
}
fn try_option_none() -> Option<u8> {
let val = None?;
Some(val)
}
fn main() {
assert_eq!(try_option_some(), Some(1));
assert_eq!(try_option_none(), None);
let v: Option<u8> = None;
assert_eq!(v, Some(None));
}
One other thing troubling me here - shouldn't try_option_none() return `None` and not `Some(None)`?
Looking at the diff for the commit, there's now a NoneError? Is there any documentation updates to look at how this works? Because AFAIK, now all matches will have to explicitly be updated to check for NoneError, otherwise they won't compile (since matches are not complete)?
= note: expected type `std::option::Option<u8>`
found type `std::option::Option<std::option::Option<_>>`
The return types of those functions are Option<u8>, but Some(None) is an Option<Option<_>>.
> there's now a NoneError?
It's not stable yet.
> now all matches will have to explicitly be updated to check for NoneError
NoneError only comes into play when you have a function that returns Result, but uses ? on an Option in the body. That code never compiled (and still doesn't on stable until Try is stabilized, and only if you have Result<T, NoneError> as your return type or some Err type that implements From<NoneError>, which you also can't write until it's stable.
"Two recent compiler changes should speed up compiles in debug mode. We don’t have any specific numbers to commit to with these changes, but as always, compile times are very important to us, and we’re continuing to work on improving them."
Can't imagine why compile times haven't been good in the past with this level of detailed analysis.
To be clear, I wrote the announcement, but don't work on compiler optimizations. Those that do tend to have a better idea than me.
But beyond that, it's ultimately about what you promise. Say something sped up compilation times 2x for most people, but slowed them 10x in a corner case. If you promise the 2x, the people for who it slowed down will be (rightfully) pretty mad.
None of these changes are as drastic as that, but still.
There's a difference between having numbers and having specific numbers to commit to… If you want raw numbers, some are tracked at http://perf.rust-lang.org
Also, incremental compilation is making very good progress. More fuzzy numbers: I'm currently working on a mid-size Rust project, where the app crate has around 10k lines. `env CARGO_INCREMENTAL=1 cargo run --release` takes 2-3 seconds depending on what I changed.
Not all improvements give you specific numbers. We track a lot of performance data, but some changes may give an extremely broad range of improvements so you can't say much about the numbers.