11 ms·
Some notes on Rust
- nercury 12y ago> Nobody seems be saying much about Rust, or if they are, the LtU search can't find it. So I'm starting a Rust topic. The reason I personally am silent about it is because there is an ongoing overhaul of standard io libraries. Honestly, my need to be adventurous dried up when I was left with few broken libs. Writing about Rust still has a risk of becoming obsolete and misleading quickly. Waiting for the real 1.0.
- twic 12y ago> I just hope the Rust crowd doesn't screw up. If i had a pound for every time i'd heard this sentiment, i'd be a rich man!
- gcv 12y agoRust's error handling looks like the Maybe monad. That seems pretty reasonable in Haskell. I'm a little surprised by the criticism in the article — is the author saying there isn't enough syntactic sugar?
- steveklabnik 12y agoIt's similar. We don't have HKT, so we can't get fully generic monads, but you can implement specific instances, like we have with Option/Result.
- mafribe 12y agoSteve, what's the reason that Rust doesn't have HKTs (higher-kinded types). Is there a technical barrier, e.g. to do with life-time inference, or is it a philosophical choice not to have them?
- steveklabnik 12y agoIt's one of our most requested features (and would be really good for collections) but it should be backwards compatible and therefore was postponed until after 1.0. Nobody has put in the work to actually make a formal RFC yet either, which is required.
- NotableAlamode 12y agoIs anybody actively working on this? I suspect that doing this well is non-trivial. Neither Tofte/Talpin nor Cyclone, both of which heavily inspired Rust's lifetimes, have HKTs as far as I'm aware.
- steveklabnik 12y agoEverything is focused on shipping a good 1.0, so no.
- NotableAlamode 12y agoI understand that 1.0 was / is a priority. But I wonder what the state of discussions about HKTs in Rust is: is this addition believed to be an easy problem in the sense that it may be a lot of work, but no major roadblocks are expected? Or are there open questions that require substantial research?
- steveklabnik 12y agoThe state is, "this should be backwards compatible, therefore, we don't need to think about it more until after 1.0." That's pretty much it. There isn't an active discussion, because we're actively discussing the things needed to ship 1.0.
- falcolas 12y agoMy experience mirrors the author's: it results in a lot of use of macros and case statements. These create a fair bit of cognitive overhead to discern what the program flow will end up being, and special syntax for unpacking values. The broad use of case statements leads to one more odd problem - knowing when, and when not, to use a `;`. Explicit returns are frowned upon, they prefer the "results from the last expression" form of returns. The `;` results in an expression returning a different value and a different type. The type system will usually catch these errors and print a helpful "perhaps you should remove the ';' from this line" message, but it's an extra bit of cognitive overhead induced by case statements. Ultimately, I think it's less about missing syntactic sugar, and more about the type system acting like an electric fence instead of a hedge in its efforts to guide the user to their destination.
- pcwalton 12y ago> Explicit returns are frowned upon That's not true in any of the code I write. I prefer explicit returns in all of my Rust code. The only time I use the "result from last expression" return is when it's the last statement in the function. The recommended way to deal with errors in Rust is "try!". Using "try!" essentially gives you the ergonomics of exceptions. You should prefer that to match or .and_then(), which are verbose.
- burntsushi 12y agoExplicit returns are not frowned upon. I don't know where you got that idea. The only thing I can think is that this: fn foo() -> bool { true } is preferred over fn foo() -> bool { return true } But that's more of a style issue. As for `;`, it's just like Standard ML. `;` is for sequencing expressions. I love it.
- falcolas 12y ago> Explicit returns are not frowned upon. I don't know where you got that idea From the docs: http://doc.rust-lang.org/book/functions.html http://doc.rust-lang.org/book/functions.html > Using a `return` as the last line of a function works, but is considered poor style
- stebalien 12y agoI don't have an account there so I'll comment here: > In particular, allocating a new object and returning a reference to it it from a function is common in C++ but difficult in Rust, because the function doing the allocation doesn't know the expected lifetime of what it returns. This is what boxes are for. A Box is a unique pointer to a value on the heap and can be used without knowing compile-time lifetimes. References and lifetimes allow you to safely return pointers to stack allocated objects. In C++, you'd have to do this: MyType value; my_function(&value); When returning references, rust uses the lifetimes instead of explicit declarations to figure out where (on the stack) `value` needs to be allocated. > Declarations are comparable in wordiness to C++. Only at interfaces where the declaration also serves as documentation. Elsewhere, types can generally be inferred. > Rust has very powerful compile-time programming; there's a regular expression compiler that runs at compile time. I'm concerned that Rust is starting out at the cruft level it took C++ 20 years to achieve. I shudder to think of what things will be like once the Boost crowd discovers Rust. Unlike C++, 1. Macros from one crate aren't imported into another unless the user explicitly requests that they be. 2. Macro invocations are clearly macro invocations. You never have to wonder if something is a function or a macro. > The lack of exception handing in Rust forces program design into a form where many functions return "Result" or "Some", which are generic enumeration/variant record types. These must be instantiated with the actual return type. As a result, a rather high percentage of functions in Rust seem to involve generics. How is this a problem? > There are some rather tortured functional programming forms used to handle errors, such as ".and_then(lambda)". Doing N things in succession, each of which can generate an error, is either verbose (match statement) or obscure ("and_then()"). You get to pick. Or you can just use ".unwrap()", which extracts the value from a Some form and makes a failure fatal. I agree that this is less than ideal. However, IMHO, this is better than Java and C++. Java: Libraries tend to bubble everything. This leads to long throws clauses in function signatures with unexpected exceptions. A user of these libraries often catches and ignores these exceptions when writing the first draft of his or her programs because they don't make sense (why handle IO Errors when using a collection?). And then, because his or her program works, he or she forget about the ignored exception cases turning them into silent errors. On the other hand, in rust, you can only return one error. When writing a function that has multiple failure modes, this forces the programmer to think about the set of failures that can happen and come up with new error type. This doesn't force the programmer to come up with a meaningful error type but it gives them the opportunity. Additionally, like in Java, Rust programmers can ignore errors (`unwrap()`). However, unlike in Java, these ignored errors are not silent, they are fatal. C++: Exceptions are unchecked and everyone I've talked to avoids them like the plague. In the end, C++ exceptions end up acting like rust's `panic!()` because programmers don't check them but are used like Java's exceptions because programmers could check them. > There's a macro called "try!(e)", which, if e returns a None value, returns from the enclosing function via a return you can't see in the source code. Such hidden returns are troubling. I agree that hidden returns can be troubling. However, in rust, only macros can lead to hidden returns, macros use a special syntax (`macro_name!(args...)`, and macros have to be explicitly imported. > All lambdas are closures (this may change), and closures are not plain functions. They can only be passed to functions which accept suitable generic parameters. This is because the closure lifetime has to be decided at compile time. The first sentence is correct but the last two are just wrong: fn takes_a_function(f: Box<Fn()>) { (f)(); } fn main() { takes_a_function(Box::new(move || { println!("hello world") })); } The `Box` allocates the closure on the heap and the `move` causes the closure to capture by value. This means that this closure (`f`) can be moved freely without lifetime restrictions because it doesn't reference the stack. However, most functions that accept closures use generics and do any necessary boxing internally to make the user's life easier. > Rust has to do a lot of things in somewhat painful ways because the underlying memory model is quite simple. This is one of those things which will confuse programmers coming from garbage-collected languages. Rust will catch their errors, and the compiler diagnostics are quite good. Rust may exceed the pain threshold of some programmers, though. Rust is a systems language. It exposes a lower-level (not simple) memory model because systems programmers need it. If you want garbage collection, you are free to roll your own (yes, you can actually do this in rust). > Despite the claims in the Rust pre-alpha announcement of language definition stability, the language changes enough every week or so to break existing programs. Re-read those claims. Alpha means fewer breaking changes and no "major" breaking changes not stability.
- Q6T46nT668w6i3m 12y ago> Despite all this, Rust is going to be a very important language, because it solves the three big problems of C/C++ that causes crashes and buffer overflows. The three big problems in C/C++ memory management are "How big is it?", "Who owns and deletes it?", and "Who locks it?". C/C++ deals with none of those problems effectively. Rust deals with all of them, without introducing garbage collection or extensive run-time processing. This is a significant advance. What? C++11/14 solves these issues.
- unfamiliar 12y agoCare to elaborate? I don't see how it does.
- azakai 12y agoYes, in particular, iterator invalidation (problem 2 in that list) seems unsolvable in C++, as far as I can see. I've hit iterator invalidation in a new c++11 codebase. Perhaps c++14 offers something to help it that I'm not aware of? edit: "unsolvable" in the sense of the language making it impossible,
- Q6T46nT668w6i3m 12y agoYeah. It isn’t enforced, but it’s easier to mitigate.
- pcwalton 12y ago> What? C++11/14 solves these issues. You're right that C++ provides a solution to the first two, but C++ locking via std::mutex isn't done in the same way as Rust: in Rust the mutex owns the data and prevents you from getting access to it unless you lock. std::mutex, however, is a separate value from the data it protects and it's up to you to coordinate access to that data. I would also argue that Rust is a better solution to the first two issues. Modern C++ does not solve the problem of use-after-free (dangling references and invalid iterators are very possible, and common in large codebases). This is something that I don't believe C++ can solve without becoming a radically different language. Furthermore, Rust forces you to use the right patterns unless you type "unsafe": this is, again, important for security, reliability, and developer productivity, reducing the amount of time you spend in the debugger.
- deleted 12y ago[deleted]
- deleted 12y ago[deleted]
- deleted 12y ago[deleted]
- alkonaut 12y ago> In particular, allocating a new object and returning a reference to it it from a function is common in C++ but difficult in Rust, because the function doing the allocation doesn't know the expected lifetime of what it returns. I'd like to see a code snippet explaining this problem.
- kam 12y agoYeah, it's unclear what he's talking about there. Normally when a function allocates a new object, it would want to return it by move (transferring ownership), rather than by reference. That doesn't involve any lifetimes. fn make_a_foo() -> Box<Foo> { Box::new(Foo { a: 5 }) } If the function allocated memory and only returned a borrowed reference, who would be responsible for freeing it? Yes, Rust will make you stop and think there, as it enforces memory safety. In cases where it does make sense to return a reference to a new object, like allocating from an arena, the lifetime ('a) of the returned reference will be the same as the lifetime of the arena. fn new_from_arena<'a>(arena: &'a TypedArena<Foo>) -> &'a mut Foo { arena.alloc(Foo { a: 5 }) } But Rust can infer the lifetime, so that can be shortened to: fn new_from_arena(arena: &TypedArena<Foo>) -> &mut Foo { arena.alloc(Foo { a: 5 }) }
- anon4 12y agoAny reason you would ever want to return a Box instead of just a Foo, which the caller can then put in a Box if so desired?
- kibwen 12y agoI can think of a case: when you want to return a type whose size is not known at runtime (a "dynamically-sized type" a.k.a. DST), then you need to stuff it behind something else whose size is known so that the compiler can statically determine how much memory to allocate before calling your function. You could use a reference for this task, but only if, as per Rust's usual rules, you have a parameter to your function whose lifetime you can tie it to. If you don't have such a parameter, then a Box is your next best bet. Somewhat related to this, there's also currently a language deficiency where you can be forced to use Box when returning a closure from a function. This will be addressed shortly after 1.0.
- frou_dh 12y ago> There's a macro called "try!(e)", which, if e returns a None value, returns from the enclosing function via a return you can't see in the source code. Such hidden returns are troubling. Strikes me as simply a very appropriate use of macros. Get tired of writing the same syntactic fragment again and again? Write a macro. Want to see what some macro is "hiding"? Look it up or expand it.
- ajanuary 12y agoThe issue is it makes it difficult to notice that a function might return when scanning through a function. Especially as it's in places that are looking for a value (e.g. assignment). At the moment it's just return and try!, but people look to the standard library for what is acceptable. When the standard library contains a macro that can return, people will write their own macros that return. It could potentially be half a dozen different macros you need to keep in your head. Personally, I go back and forth on it. Hopefully it will turn out fine.
- deathanatos 12y ago> The issue is it makes it difficult to notice that a function might return when scanning through a function. What other solutions are there? The only other approach to error handling I've seen is exceptions (e.g., C++, Java, C#, JS…), and if you don't like `try!` because it is a "hidden return", you certainly won't like exceptions. At least with Rust's macros, I know that in the absence of one, there is no return; in the presence, there might be. Exceptions in most languages make no guarantee.
- ajanuary 12y agoIt's a trade off. For the trouble of exceptions I get nice benefits like stack traces (though there are proposals to add stack traces to Rusts error handling). There are times when I really like exceptions, and times (such as trying to trace through an execution path) that I'm irritated by them. What other solutions are there? Well, you could make the return explicit: let z = try!(x / y, onerror = return) There are obvious downsides, such as added verbosity, and the need to figure out keyword arguments in macros, and do you allow access to the error value etc. but at least I can grep for/highlight "return". It also makes the meaning of the slightly confusingly named "try" more obvious (again, I'm vaguely aware of proposals that would change the name). I think ultimately try! is a good thing, but I don't think it's trivially "a very appropriate use of macros". It's a considered use given some difficult trade-offs.
- Animats 12y agoWow. I wrote that article on LtU last night, after going over there to see what the language theorists were saying about Rust. (And after, for the third time in three weeks, having my Rust code fail to compile because the Rust crowd changed the language again, after the "alpha release" and its claims of stability: http://blog.rust-lang.org/2014/12/12/1.0-Timeline.html http://blog.rust-lang.org/2014/12/12/1.0-Timeline.html) I wasn't expecting it to be picked up on Hacker News. Rust is going to be very important. The ownership system is a major step forward in language design. It's a huge improvement over C/C++. It's not easier to write than C++. Rust may feel clunky for people coming from Javascript, Python, Ruby, and PHP. Having to think about lifetime issues for mere strings is a new cognitive load. The big win with Rust is that most of the errors are caught at compile time. This is Rust's big advantage, but alien to scripting programmers. The Rust compiler report errors in three phases. First you get all the syntax errors, and until the syntax is perfect, that's all you get. Then you get all the type errors, and until the type issues are perfect, that's all you get. Then you get the ownership errors. Ownership is a global analysis; ownership problems involve at least two points in the program. The compiler produces good, but very wordy error messages. (Hint to Rust developers: put in a line length limit and word wrap for long compile time error messages.) If your ownership design is faulty, the result is likely to be "fighting with the borrow checker", because the problem isn't local, and just fixing the compiler-reported error will make the problem pop up elsewhere. The cleverness of the ownership system is impressive, but some programmers are going to feel like they're being hammered by it. Successful C++ programmers won't have a major problem with this. It may be tough on the Javascript crowd. Rust requires some advance planning, which may be incompatible with "agile" development. It's also difficult to port code from other languages to Rust without rethinking the ownership and bounds logic. There is a port of Doom to Rust. It has a lot of unsafe code, because Doom's internal memory structures are not directly compatible with Rust's. Such problems will recur as big packages with delicate internals are ported over to Rust. This is partly a documentation problem. The current tutorial (http://doc.rust-lang.org/book/hello-cargo.html http://doc.rust-lang.org/book/hello-cargo.html) is relentlessly upbeat and glosses over too many of the hard problems. Once some third-party books have been written, that situation should improve.
- NotableAlamode 12y ago
- hurin 12y agoI tried to learn a bit of rust and I couldn't shake the feeling that rust is a very ugly language, like uglier than c ugly.
- jml7c5 12y agoWhat makes you say this? And what is your prior language experience?
- Animats 12y agoI tried to learn a bit of rust and I couldn't shake the feeling that rust is a very ugly language, like uglier than c ugly. What makes you say this? And what is your prior language experience? Right there is the problem. There are many programmers for whom it's their day job. Others depend on their code doing something useful and important. But they're not theoreticians, don't have advanced degrees in CS, and haven't written in a dozen languages. A new language has to be usable by them to get traction. The Rust community, at this point, is mostly people who know several programming languages and want to try a new one. Look at the comments above from people who compare type systems in different languages and are aware of the strengths and weaknesses of different approaches. Note the references to obscure languages and research papers most programmers have never heard of, let alone used or read. This is not the target market for a new language, if it is to be a success. It has to be used by people who don't debate language theory issues while the Super Bowl is on.
- dmix 12y agoPeople said this about Clojure and I never fully understood why. I've dabbled in Rust and while I've always loved Ruby for it's aesthetic I never had a problem with Rust. It suits the language. Feels like you're writing serious code in a serious language. Which is appropriate.