1. 27

  2. 17

    I wrote a long comment when I saw this on HN, I’ll post it here too:

    I am utterly thankful for new experience reports on Rust, especially for ones this well-written. Generally speaking, inaccuracies in such things are our fault, not the writers', due to a lack of documentation and or good examples.

    With that being said, a few notes:

    It runs about five times slower than the equivalent program

    I’d be interested in hearing more about how these were benchmarked. On my machine, they both run in roughly the same time, with a degree of variance that makes them roughly equivalent. Some runs, the iterator version is faster.

    It’s common to forget to turn on optimizations, which seriously impact Rust’s runtimes, LLVM can do wonders here. Generally speaking, if iterators are slower than a loop, that’s a bug.

    Rust does not have tail-call optimization, or any facilities for marking functions as pure, so the compiler can’t do the sort of functional optimization that Haskell programmers have come to expect out of Scotland.

    LLVM will sometimes turn on TCO, but messing with stack frames in a systems language is generally a no-no. We’ve reserved the ‘become’ keyword for the purpose of explicitly opting into TCO in the future, but we haven’t been able to implement it because historically, LLVM had issues on some platforms. In the time since, it’s gotten better, and the feature really just needs design to work.

    Purity isn’t as big of a deal in Rust as it is in other languages. We used to have it, but it wasn’t very useful.

    But assignment in Rust is not a totally trivial topic.

    Move semantics can be strange from a not-systems background, but they’re surprisingly important. We used to differ here, we required two operators for move vs copy, but that wasn’t very good, and we used to infer Copy, but that ended up with surprising errors at a distance. Opting into copy semantics ends up the best option.

    how that could ever be more useful than returning the newly-assigned rvalue.

    Returning the rvalue ends up in a universe of tricky errors; not returning the rvalue here ends up being nicer. Furthermore, given something like “let (x, y) = (1, 2)”, what is that new rvalue? it’s not as clear.

    I’ve always thought it should be up to the caller to say which functions they’d like inlined,

    This is, in fact, the default. You can use the attributes to inform the optimizer of your wishes, if you want more control.

    It’s a perfectly valid code,

    In this case it is, but generally speaking, aliasing &muts leads to problems like iterator invalidation, even in a single-threaded context.

    but the online documentation only lists the specific types at their five-layers-deep locations.

    We have a bug open for this. Turns out, relevant search results is a Hard Problem, in a sense, but also the kind of papercut you can clean up after the language has stable semantics. Lots of work to do in this area, of course.

    Rust won’t read C header files, so you have to manually declare each function you want

    The bindgen tool can help here.

    My initial belief was that a function that does something unsafe must, itself, be unsafe

    This is true for unsafe functions, but not unsafe blocks. If unsafe were truly infectious in this way, all Rust code would be unsafe, and so it wouldn’t be a useful feature. Unsafe blocks are intended to be safe to use, you’re just verifying the invariants manually, rather than letting the compiler do it.

    but until a few days ago, Cargo didn’t understand linker flags,

    This is not actually true, see http://doc.crates.io/build-script.html for more.

    the designers got rid of it (@T) in the interest of simplifying the language

    This is sort of true, and sort of not. @T and ~T were removed to simplify the language, we didn’t want language-support for these two types. @T’s replacement type, Gc<T>, was deemed not actually useful in practice, and so was removed, like all non-useful features should be.

    In the future, we may still end up with a garbage collected type, but Gc<T> was not it.

    Rust’s memory is essentially reference-counted at compile-time, rather than run-time, with a constraint that the refcount cannot exceed 1.

    This is not strictly true, though it’s a pretty decent starting point. You may have either 1 -> N references, OR 1 mutable reference at a given time, strictly speaking, at the language level. Library types which use unsafe internally can provide more complex structures that give you more complex options.

    That’s at least my initial thoughts. Once again, these kinds of reports are invaluable to us, as it helps us know how we can help people understand Rust better.

    1. 8

      Rust tag?

      1. 3

        heyyyy typo - @kyle, could you replace #ruby with #rust, please?

      2. 2

        As an added bonus, there’s a feature called “type erasure” so you can force a dynamic dispatch to prevent compiling lots of pointlessly specialized functions. This is a good compromise between flexibility and performance, while remaining more or less transparent to the typical user.

        Yet, this is one of the most not-liked things about Java…

        1. 6

          While that’s true, it’s very different, since in Java, everything is boxed by default. You usually statically dispatch in Rust, you rarely dynamically dispatch.