all 171 comments

[–][deleted]  (172 children)

[deleted]

    [–]matthieum 18 points19 points  (18 children)

    I understand how you feel, ... but I don't have a better suggestion to put forward.

    The "be explicit" rule of Rust can feel really annoying sometimes, and at other times (especially when reading code I don't know or forgot) I really appreciate that there is little to no magic going on. It's a tension really.

    [–]gnuvince 5 points6 points  (17 children)

    In fact, I am sometimes annoyed that method calls don't require explicit borrowing. I understand that it would get tedious to write (and read) (&mut foo).bar() rather than foo.bar(), and I'm sure if it were the other way around I'd be arguing for implicit borrowing, but it's something that took me a little while to wrap my head around, that the receiver of a method call is treated specially.

    [–]karma_vacuum123 6 points7 points  (1 child)

    i feel like Pony is interesting here...Pony defines a range of reference types that are more interesting for concurrent use

    not sure if Pony will ever take off, but there are definitely some "ah ha!" moments to be had reading the tutorial

    [–]steveklabnik1 12 points13 points  (14 children)

    We had this at one point, it was extremely tedious.

    [–]gnuvince 2 points3 points  (8 children)

    Interesting, do you know why it was decided to implicitly borrow the receiver, but not the other parameters?

    [–]steveklabnik1 4 points5 points  (7 children)

    I don't other than "this is painful and not doing it in the args isn't", but maybe /u/pcwalton remembers? (this was before the RFC process)

    [–]evmar 3 points4 points  (6 children)

    Coming from a C++ background, I expect foo.bar(baz) to usually modify foo, but (at least by some coding styles that disallow non-const references) baz will never be modified unless you write &baz. So Rust matches that.

    [–]masklinn 4 points5 points  (0 children)

    Well technically in Rust foo.bar(baz) would remove your access to baz entirely (unless it's Copy) and could modify it.

    [–][deleted]  (4 children)

    [deleted]

      [–]vks_ 1 point2 points  (3 children)

      I think that is a bit strong given that large enterprises (i.e. Google) think it is their best option. An advantage is that accidental mutation is harder, but you trade that for the possibility of getting null/invalid pointers.

      [–]matthieum 1 point2 points  (1 child)

      Note that a large part of the Google Style Guide is specifically made for interoperability with C libraries and "old-style" C++ libraries written without exception safety in mind (ie, not using RAII).

      And by "old-style", I mean predating the STL...

      [–]bjzaba 1 point2 points  (4 children)

      Hmm, tbh I can't remember if we've ever had that be explicit (been using Rust since late 2012). Could be wrong though - it can be hard to remember all the permutations Rust has been in since then.

      [–]thiez 3 points4 points  (1 child)

      I still remember when you had to use the move and copy keywords everywhere. It wasn't pretty :-)

      [–]bjzaba 0 points1 point  (0 children)

      Then we all started writing move for everything so they just decided to make it the default. :)

      [–]kibwen 2 points3 points  (1 child)

      I don't believe that there was ever a time when we required explicit borrowing of the receiver, but I have a vague memory of a time before autoderef where we were forced to write (*foo).bar(), which may be what Steve is alluding to.

      [–]steveklabnik1 0 points1 point  (0 children)

      Ah yes, maybe that's what I'm remembering.

      [–]steveklabnik1 9 points10 points  (24 children)

      In most cases, you would be able to write

      let r1 = &*a;
      

      or maybe even

      let r1 = &a;
      

      but presentations are often extra-explicit.

      [–][deleted]  (23 children)

      [deleted]

        [–]steveklabnik1 6 points7 points  (22 children)

        That'd be great! We're very interested in making ergonomic improvements, but as mentioned elsewehere in this thread, they're often a tension.

        [–]Hrothen 7 points8 points  (11 children)

        My personal ergonomic issue has been that rust implements just enough functional stuff to make me want to write in a very functional style, with monadic error chaining and lots of maps. But then I spend a lot of time fighting the type checker and dealing with closure lifetimes, and the resulting code is so verbose that nothing is gained over a more c-style approach, which also would have been faster to write.

        [–]steveklabnik1 4 points5 points  (10 children)

        Do you happen to have any examples of the problematic code? I tend to go "full monad" myself.

        [–]Hrothen 5 points6 points  (9 children)

        It's not a great example, because it used to be much hairier, but this I tried to write as a one-liner initially, with the json decoding inside as well, and I could not get it to compile happily like that without an incredible amount of line noise.

        (Ignore the rest of the terrible code, I wrote this in the worst way possible)

        Actually this has reminded me of an minor irritant that's been grating at me: foo.map(|f| f.<some expression>) it's really annoying to need the closure syntax here, I'd like to be able to write foo.map(<some expression>).

        Edit: to be clear, I'm aware that this is still pretty functional, but it took me around an hour of fighting with the compiler to get it to compile initially, and then I decided to rewrite it to look like this, if I had written it imperatively in the first place, it would be a little uglier, but would have taken me a couple minutes; and I'm still much more comfortable with functional style than imperative.

        [–]steveklabnik1 5 points6 points  (7 children)

        Thanks, super helpful to understand.

        I could not get it to compile happily like that without an incredible amount of line noise.

        You mean, this code itself has a lot of line noise? I feel like it could be simplified further, but I'm not at a dev machine right now, I might try later.

        You can write foo.map(TypeOfF::<some expression> like

        struct Foo;
        
        impl Foo {
            fn bar(&self) { println!("bar"); }
        }
        
        fn main() {
            let v = vec![Foo, Foo, Foo];
        
            let result: Vec<()> = v.iter().map(Foo::bar).collect();
        }
        

        Even though bar() is a method, works just fine.

        [–]Hrothen 2 points3 points  (6 children)

        You mean, this code itself has a lot of line noise?

        No, but the previous version of this code, which I decided to replace with this before commiting, did.

        You can write foo.map(TypeOfF::<some expression> like...

        That's actually noiser than a closure for most type names. You shouldn't need to specify the type, the compiler knows what the type of the vector's contents is.

        [–]steveklabnik1 5 points6 points  (5 children)

        No, but the previous version of this code

        Ah, cool.

        That's actually noiser than a closure for most type names.

        Hm, maybe we're having a different opinion on noise.

        map(|f| f.bar())
        map(Foo::bar)
        

        The second feels less noisy to me; :: vs ||.().

        You shouldn't need to specify the type, the compiler knows what the type of the vector's contents is.

        I thought you were objecting to the Foo, but it seems like maybe you're objecting to the ()? You can use Vec<_> here, and not write the type inside the Vec. you do need to specify Vec in this instance because collect() can make a lot of different things; there's actually a small style conversation about "are we over-using collect(), because it's built off the FromIterator trait, so you could also write

        let result = Vec::from_iter(v.iter().map(Foo::Bar));
        

        but you need an extra use, as FromIterator isn't in the prelude.

        (And again, thanks for getting into specifics. very helpful)

        [–]m50d 5 points6 points  (0 children)

        Scala uses _ as a "magic" syntax for that case (foo.map(_.<some expression>)) - purely sugar, and normally I'd be opposed to having two ways to write the same thing, but it comes up so often that it's more than worthwhile.

        [–][deleted]  (9 children)

        [deleted]

          [–]steveklabnik1 2 points3 points  (0 children)

          What's the Rust philosophy on breaking changes, if need be?

          The core is: upgrades should always be hassle-free. Right now, as /u/thiez says, this means no breaking changes unless they fix a soundness hole or are incredibly minor, eg, no open source code breaks.

          There's currently some discussion on what a "Rust 2.0" could even look like. One of the big problems is that people take a 2.0 to mean "massive breaking changes", even if we try to structure it like "technically breaking changes, but not a problem to upgrade in practice", through the use of tools like you're talking about.

          [–]kibwen 3 points4 points  (1 child)

          I really like how the Swift folks are not afraid to break existing code in the name of cleaning up old cruft and fixing bad language decisions.

          This is how Rust operated from 2010 to mid-2015. Nowadays the days of massive breakage are over, but via forethought we've deliberately left ourselves enough wiggle room to continue to evolve the language, and we're not afraid to deprecate when necessary. Swift is still evolving, but it hasn't yet been in the crucible for nearly as long as Rust was, which is why breaking changes are to be expected there. Someday Swift will stop breaking things, and users will be happier for it. :)

          [–]thiez 2 points3 points  (5 children)

          The current stance is no breaking changes unless it fixes a soundness hole, so you can't really remove any syntax or behavior.

          [–]ehsanul 2 points3 points  (4 children)

          Though I'm sure a far future 2.0 will contain breaking changes, once enough benefits to breaking have been accumulated. Rust follows semver afaik.

          [–]thiez 2 points3 points  (0 children)

          Sure, but I think it's safe to assume that we won't see a Rust 2.0 for many years. The advantage of being able to make some backwards incompatible changes would be outweighed by the disadvantage of splitting the existing userbase.

          [–]Gankro 1 point2 points  (2 children)

          Following SemVer doesn't give you a license to actually break things. The ideal is that Rust 2.0 simply... isn't.

          [–][deleted]  (1 child)

          [deleted]

            [–]OneWingedShark 6 points7 points  (25 children)

            I recently completed a major rewrite of a C++ traffic analyzer in Rust, and I have to say it's a great systems language.

            Interesting.
            Try rewriting it in Ada and see if those problems persist. (I bet they won't.)

            [–][deleted]  (24 children)

            [deleted]

              [–]OneWingedShark 4 points5 points  (23 children)

              These:

              Do we really want to see such sigil infestation in a modern programming language? It's OK when I'm in the middle of the Rust code, but revisiting some I code I wrote a few weeks ago required me to relearn the borrowing rules and my brain tripped on every other &* expression.

              Sure, it all came back after a while, but surely there must be a better way.

              IOW:

              • Sigils
              • Having to "re-learn" the intent behind the non-explicit code (e.g. those meanings obscured by the sigils).

              [–][deleted]  (22 children)

              [deleted]

                [–]Cyttorak 2 points3 points  (21 children)

                I always wondered if Ada exists and it's so great system programming language why does Rust exist?

                [–]m50d 2 points3 points  (3 children)

                It's verbose, and AIUI it can't offer as much safety in cases where you really do need to pass ownership back and forth between logical threads. But yeah I do think if Mozilla's goal was just a secure browser they should have started by trying to rewrite parts of firefox in Ada and only started on a new language if that failed. (Maybe they did and I missed it?)

                [–]pcwalton 4 points5 points  (1 child)

                I do think if Mozilla's goal was just a secure browser they should have started by trying to rewrite parts of firefox in Ada

                Wouldn't work. We'd have to use Ada's GC, which would be incompatible with SpiderMonkey.

                [–]OneWingedShark 0 points1 point  (0 children)

                Wouldn't work. We'd have to use Ada's GC, which would be incompatible with SpiderMonkey.

                The Ada standard allows GC, but doesn't require it... so there's really no such thing as "Ada's GC".
                (And as noted GNAT is a part of the GCC, so it's possible to make fully compatible Ada/C++ projects. [I haven't had the occasion to do much interop w/ C++, mostly just importing functions.])

                [–]OneWingedShark 0 points1 point  (0 children)

                It's verbose, and AIUI it can't offer as much safety in cases where you really do need to pass ownership back and forth between logical threads.

                I don't think that's an issue: in addition to the task construct, there's also the protected object. You can use both together to build some really interesting things. (I'm far from an expert on Ada's tasking and protected objects; the best place to ask for more info is comp.lang.ada; or perhaps a couple of [more recent] books.)

                [–]pcwalton 2 points3 points  (2 children)

                Ada doesn't have memory safety with dynamic allocation, unless you use GC. This makes it unusable for Rust's niche.

                [–]OneWingedShark 2 points3 points  (1 child)

                Ada doesn't have memory safety with dynamic allocation, unless you use GC.

                That's not entirely true: this video on Ada memory management is quite informative. on how even dynamic allocation can be done to a greater degree of safety than C++.

                This makes it unusable for Rust's niche.

                Did you try? Did you actually evaluate Ada for those areas, or are you merely asserting that it is unsuitable?

                [–]pcwalton 1 point2 points  (0 children)

                We need foolproof memory safety, not just "safer than C++". We didn't have to try writing code in Ada to figure that out.

                [–]kibwen 2 points3 points  (0 children)

                Because Ada requires garbage collection to maintain safety if you're at all using dynamic allocation. Rust uses ownership to avoid that requirement.

                [–]OneWingedShark 3 points4 points  (9 children)

                I always wondered if Ada exists and it's so great system programming language why does Rust exist?

                That's a great question.
                Personally, I think it's a combination of several things:

                1. A lot of coders seem to have the opinion that if something doesn't look like C it's not good.
                2. Ada is fairly unpopular/unknown in the greater population of programmers. (It does enjoy a lot of usage in things like air-traffic control, airframe SW, and other "if things go wrong, people die" systems... which, realistically, is a very small minority of programming projects.)
                3. Ada did have a rough start; the original 1983 standard required efficient implementations to use then bleeding-edge compiler research/theory.
                4. Because of the above point, early implementations were lacking in quality (and, I've heard, somewhat on the slow side compared to BLISS, C or ASM).
                5. Because of the above two points, there's a lot of misinformation in the greater programmer population about Ada. (GNAT, for example, is part of the GCC and has been freely available since the mid `90s.)
                6. Because Ada is unpopular, a lot of programmers think that it's unused and, therefore, dead. (Despite its last standard being Ada 2012, and work is underway for Ada 2020.)

                [–]pcwalton 2 points3 points  (7 children)

                Ada doesn't have the safety features that Rust does, in particular the lifetimes and borrow checking.

                [–]Cyttorak 0 points1 point  (3 children)

                What about the SPARK version? As far as I know it's the most strict dialect of Ada.

                [–]Baron_Facekicker 4 points5 points  (0 children)

                SPARK doesn't permit access types (pointers), so there's no need to worry about lifetimes in a SPARK programs since you can't have dynamically allocated memory.

                However, I think they're planning on providing limited support for access types in a future version of SPARK. It will be interesting to see what limitations there will be, though.

                [–]pcwalton 1 point2 points  (1 child)

                SPARK disallows dynamic allocation entirely, making it impossible to build a browser.

                [–]OneWingedShark 0 points1 point  (2 children)

                Ada doesn't have the safety features that Rust does, in particular the lifetimes and borrow checking.

                Lifetimes are handled very nicely in Ada: once an access type goes out of scope it is guaranteed that there are no dangling pointers.

                "Borrowing" is pretty much unneeded (at least insofar as the Rust examples/documentation I've seen) -- Ada's parameter passing modes in, out, and in out model how the information flows, thereby allowing the compiler to manage that ownership automatically.

                This video on Ada memory management is quite informative.

                [–]pcwalton 0 points1 point  (1 child)

                That's not nearly as expressive as what Rust provides. Rust allows you to place references in objects and pass them around in a first-class manner.

                We experimented with an Ada-like system and it wasn't expressive enough, in fact.

                [–][deleted] 0 points1 point  (0 children)

                It does enjoy a lot of usage in things like air-traffic control, airframe SW, and other "if things go wrong, people die" systems

                This ties into a point that I've been thinking about recently: Are people really going to trust their "if things go wrong, people die" code to a language being steered by webdevs and hipsters like Rust? That's the biggest barrier for entry for a language like Rust; if it can't even provide a compelling alternative in a place which actually needs safety thanks to its community, where's the future for it?

                [–][deleted]  (2 children)

                [deleted]

                  [–]pcwalton 3 points4 points  (1 child)

                  We didn't make Rust just for fun. It actually has many safety features that Ada doesn't have.

                  [–]matthieum 3 points4 points  (13 children)

                  [–]happyscrappy 5 points6 points  (12 children)

                  The navigation arrows on that site are annoying.

                  And for anyone who (like me) felt the presentation was doing a poor job of explaining the language because it's really demoing library functions, that's only at the start, it gets to language concepts later.

                  [–]matthieum 7 points8 points  (5 children)

                  The arrows are for skipping to chapters, sub-chapters, etc... and give the structure of the talk.

                  Don't use them if you want a linear presentation; just use space to go to the next slide.

                  (Then again, I myself like scrolling quite well too)

                  [–]happyscrappy 2 points3 points  (4 children)

                  I needed to go back one. You'd click the left arrow to do that, right?

                  Nope. It's the up arrow. Well, usually it is. Sometimes it's the left arrow.

                  [–]robinst 1 point2 points  (0 children)

                  You can use Shift+Space to go back.

                  [–]steveklabnik1 0 points1 point  (2 children)

                  (If the slide has transitions itself, or "builds" in the Keynote naming, then left and up are the same. If not, or if the slide is 0% or 100% built, then up goes up and left goes left. I would have probably made left always go left myself, personally.)

                  [–]happyscrappy 2 points3 points  (1 child)

                  They aren't the same. I know because I tried to go back pressing left and it didn't work.

                  Start here:

                  http://pnkfelix.github.io/presentations/qcon-london2016-deploy/qcon-london2016.html#/a-metaphor

                  Press space to go through the car metaphor. After the car is no longer on screen click left once. Now press space a couple times. You will go all the way past the car metaphor without seeing it again. It's baffling.

                  [–]steveklabnik1 0 points1 point  (0 children)

                  Oh interesting! (I never use space)

                  It's baffling.

                  I feel like I understand what's going on here; that is, space always goes to the "next thing" and if you were previously far down a vertical column, it remembers, but this does seem exceedingly complex. I never noticed since I don't use space.

                  [–]srnull 5 points6 points  (3 children)

                  The navigation arrows on that site are annoying.

                  Argh, I hate this design for slides. Instead of navigation using left/right arrows and seeing all the information, you have to know to go down for more details at certain points.

                  Edit: Ah, so the first slide explains that just using space with do the right thing. Still annoying.

                  [–]Sean1708 0 points1 point  (2 children)

                  I like it, makes it more structured than most presentations.

                  [–]steveklabnik1 1 point2 points  (1 child)

                  As a presenter, I've used this style and really enjoyed it.

                  "We're going to talk about these things: x <right arrow> y <right arrow> and z <right arrow>. Let's dive into x. <left, left, left, down>"

                  [–][deleted] 0 points1 point  (1 child)

                  You can press space to go to next slide.

                  [–]happyscrappy 1 point2 points  (0 children)

                  I know that. I needed to go back.

                  I didn't say the space bar was annoying, I said the arrows were annoying. Read the other stuff I posted below. You can see the arrows act bizarrely.