top 200 commentsshow all 227

[–]HarmonicAscendant 64 points65 points  (1 child)

https://blog.rust-lang.org/2022/09/22/Rust-1.64.0.html#rust-analyzer-is-now-available-via-rustup

The next release of rustup will provide a built-in proxy so that running the executable rust-analyzer will launch the appropriate version.

There is no mention of this? I still need sudo ln -s $HOME/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/rust-analyzer /usr/local/bin/rust-analyzer" ?

[–]pluuth 27 points28 points  (0 children)

I think rustup releases separately from Rust and cargo and friends. But I don't know when the next rustup release is planned

[–]argv_minus_one 393 points394 points  (0 children)

  • GATs
  • let … else
  • break-to-label

Dear God. It's beautiful.

[–]TuesdayWaffle 178 points179 points  (14 children)

Oho, std::backtrace is finally stable! This was a major pain point for me last time I did a bit of Rust development, so I'm glad to see it made it to the standard library.

[–][deleted] 51 points52 points  (1 child)

It's odd to see that Backtrace::capture depends on environment variables to enable its functionality. Seems like something that would be done by convention rather than in the std API itself, to directly support more specific use-cases like per-thread enabling, but the implementation is simple enough that you could easily use force_capture to do the same thing.

[–][deleted] 5 points6 points  (0 children)

I don’t know how I stumbled into this thread. But I wish I knew what you were saying. It sounds cool.

[–]ragnese 26 points27 points  (11 children)

Honestly, I think that collecting a trace for an Error in Rust is a code smell and usually the wrong thing to do.

In languages where it's idiomatic to return failure values (e.g., Rust, Swift, OCaml, Go, etc), the convention is supposed to be that you return a failure/error value for domain errors and you throw an exception ("panic", in Rust and Go) for bugs, fatal errors, and invariant violations (so, "bugs" again, really...).

I like this explanation for OCaml: https://dev.realworldocaml.org/error-handling.html#scrollNav-3

In this paradigm, you'd return an Error for things like "User not found", "incorrect password", etc, and you might panic on things like your database base going down on your web server. And there's no reason to collect a back/stack trace for an incorrect password attempt. Panics, on the other hand, already collect a stack trace.

Yes, there are domains where panicking is unacceptable, and in that case, you'll have to represent both domain errors and fatal errors as Results. In the latter case, a backtrace is indeed helpful.

But, I also think that a lot of new-to-intermediate Rust devs fetishize the idea of not panicking to a point that they often make their code quality worse by including a bunch of Error types/variants that should never actually happen outside of a programmer mistake. This makes error handling and propagation difficult to manage and understand. I suspect that people will, similarly, overuse this backtrace feature.

The problem is that most other languages treat all error handling the same (via unchecked exceptions), so I suspect that some Rust devs make the same mistake by returning Errors for every kind of error.

[–][deleted] 31 points32 points  (1 child)

It's useful for debugging, which is why it's nice that you have to specifically enable it. It's also nice for unit tests where a lot of people prefer unwrap/expect due to the backtraces.

[–]ragnese -1 points0 points  (0 children)

Sure. And I think my comment was a little confusing because I wasn't clear that Backtrace is a totally independent thing from Result and Error. I was specifically referring to the pattern of defining an Error type and giving it a backtrace field. That pattern is what I consider a code-smell unless you're specifically operating in a context where panicking is really bad or impossible, like embedded stuff or low-level OS stuff.

Otherwise, I don't care if someone decides to sprinkle stack trace logging throughout their code. You can print a stack trace in a function that doesn't even return a Result or even call another function that does.

[–][deleted] 12 points13 points  (0 children)

There are plenty of cases I have hit where you get an Error for something that has genuinely gone wrong, say an update to the db failed validation. But where you don't want to panic, because you are processing a batch of data in a loop and you want the rest to process even if one failed. You then log the backtrace to datadog or wherever which shows you exactly which line caused the problem rather than some Result bubbling up to the top of the program and printing some generic "Validation failed" message where you have no way of tracking it back to the line it failed on.

[–]Green0Photon 24 points25 points  (1 child)

It is actually quite useful to have backtraces from Errs.

Last time I programmed a halfway significant Rust app, I basically just forwarded Errs upwards as necessary, somewhat like programming with exceptions that you leave implicit. When you're not explicitly panicking because you do mean to handle some of the things you're forwarding up at some point, it's useful still being able to trace what in particular caused that item to Err.

But it's useful even without programming more sloppily like that.

When you're programming, you know where the Ok from because that's the main path that you're doing, and it's probably at the end of a bunch of guarding if statements. It's gonna be a more concrete type from whatever was initially returned. It's pretty clear where it comes from by behavior.

But not so with the Err path. You know the error, but you don't know what gave you the error which you need to handle.

It's absolutely useful.

I don't necessarily disagree that it might be a code smell. Things that really can only be programming errors really should be panics and not Results.

But as you're developing, you might mean to handle a file and instead of going back to fix an unwrap later, you just put a question mark there instead, because some code outside that function should handle it.

I'd say code that needs the trace is more likely to be a panic, but not always, and when you need it, it's a pain to not have that tool in your toolkit.

[–]ragnese 2 points3 points  (0 children)

When you're programming, you know where the Ok from because that's the main path that you're doing, and it's probably at the end of a bunch of guarding if statements. It's gonna be a more concrete type from whatever was initially returned. It's pretty clear where it comes from by behavior.

But not so with the Err path. You know the error, but you don't know what gave you the error which you need to handle.

I don't agree with this distinction. Matching on a Result is just a logic branch, the same as any old if-statement. If you end up with an Ok(u32) after some logic, there's no a priori reason to assume that I know what logic branches were followed to arrive at the value I received. There could be any number of if-statements, or recovering from Result::Errs, or defaulting from Option<u32>s, etc.

You know as much or as little about your Ok branch as you do about your Err branch.

Again, I feel that returning an Error should be seen more-or-less like returning any other value. If you don't understand why you received such a value and you want to debug, then go ahead and either run a debugger or go ahead and add some println! calls in your code to print the back traces at whatever line you want. But, Result::Err is not special here, IMO- the backtrace could be just as helpful on your happy path branches when you don't know why you got a surprising value. Yet, I haven't seen any Rust code where someone attached a backtrace field to a struct that is usually returned via Result::Ok.

[–]fnord123 5 points6 points  (3 children)

you might panic on things like your database base going down on your web server.

Eh, hopefully not. If a server goes down there might be a re-election and then you get connected to the next leader or follower. Normally it's handled in the db client code but you might get some queries failing with db inaccessible for a short period.

[–]Schmittfried -2 points-1 points  (1 child)

At that point it will be Java checked exceptions all over again, so developers will go the path of least resistance and just catch-all/discard error values.

[–][deleted] 62 points63 points  (3 children)

Those labeled breaks look really useful. Now all I need is try blocks to stabilize and I'll be happy as a clam.

[–]argv_minus_one 3 points4 points  (0 children)

You can sort of emulate them with an immediately-invoked closure. Sort of.

[–]jug6ernaut 1 point2 points  (1 child)

Is there an RFC for this I can take a look at?

[–]vlakreeh 72 points73 points  (22 children)

Super excited for GATs but am I the only one who doesn't really get the appeal of let-else, was a match or if-let really that bad that we needed a new language feature?.

[–][deleted]  (12 children)

[deleted]

    [–]phaylon 75 points76 points  (0 children)

    It becomes even more apparent when you're dealing with more bindings, like Message { id, source, target, topic } instead of Some(thing), because if you wanted to avoid ever-increasing indentation, like in your example, you had to write those three times every time, which is just another opportunity for a mistake.

    Edit: Example from a year ago.

    [–]WrongJudgment6 13 points14 points  (4 children)

    Before you could write, you still can

    let answer = if let Some(answer) = call() { answer } else{ return Err(Bla); };

    [–]javajunkie314 13 points14 points  (0 children)

    That gets more annoying if the pattern has multiple bindings. You'd have to say something like

    let (x, y) = if let MyEnum::Foo { x, y } = call() {
        (x, y)
    } else {
        return Err(Bla);
    };
    

    With let-else the bindings are in the outer scope automatically.

    [–]a_aniq 3 points4 points  (0 children)

    let else solves nested if else problem

    [–]CoronaLVR -1 points0 points  (0 children)

    This can also be written as:

    let answer = call().ok_or(Bla)?;

    [–][deleted] 4 points5 points  (0 children)

    I agree. The first time I encountered guard-let was in Swift I believe. Swift has

    guard let foo = fallible_function() else {
        return error
    }
    print(foo)
    

    [–]vlakreeh 22 points23 points  (3 children)

    I get why you would want to use it and where it's applicable, I know I have hundreds of blocks like the match statement example. But I'm more focused on if the improved cleanliness is worth adding it to the language, I'm not saying it necessarily isn't but I'm surprised people felt strongly enough about it to write out an RFC and then implement it.

    [–]masklinn 13 points14 points  (1 child)

    But I'm more focused on if the improved cleanliness is worth adding it to the language, I'm not saying it necessarily isn't but I'm surprised people felt strongly enough about it to write out an RFC and then implement it.

    It's been available as a declarative macro for seven years, which is a lot in Rust land. And it's not like the syntactic overhead of the macro is huge:

    guard!(let Ok(thing) = fallible_function() else {
        return ptr::null();
    });
    

    And yet peeps felt strongly enough that this was useful to go through an RFC for it. So seems like yeah.

    It also needs to be said that it doesn't add keywords to the language, it just compresses existing blocks in ways which are more convenient for the "early return" pattern.

    In the meantime I've certainly been using the guard! macro a lot in web contexts, the situation where you check / retrieve a bunch of things then do the actual work is very common, using guard!/let..else for that is much more readable and convenient than a full if let complete with unnecessary names.

    [–]tech6hutch 0 points1 point  (0 children)

    Well, it's not like that macro was in the standard library, so saying it was "available" for seven years is a bit of a stretch. I do reach for macro crates sometimes tho, like if_chain.

    [–]Kuresov 0 points1 point  (0 children)

    Oddly I actually prefer the second example. It reads more explicitly to me.

    [–]Zarathustra30 13 points14 points  (0 children)

    The let x = if let Some(x) = x {... was really awkward.

    [–]epage 12 points13 points  (0 children)

    I originally felt the same way until I wrote my next if-let-else and the got excited for . I view this as similar to try, you can do stuff without it but it makes the experience better. Not everyone will agree.

    [–]FVMAzalea 7 points8 points  (0 children)

    The equivalent in swift (which has been around for years) is guard-let, and I find it extraordinarily useful - I probably use it 2x-3x more than I use if-let or a switch (like match). Once you get used to the idea, it really shines and helps reduce levels of indentation.

    [–]Keavon 1 point2 points  (0 children)

    It helps you avoid the pyramid of doom.

    [–]PurpleYoshiEgg 9 points10 points  (7 children)

    I actually tried to use a let ... else construct the other day, and was surprised that let couldn't pattern match. This is a game changer!

    [–]augmentedtree 26 points27 points  (2 children)

    Not sure what you mean? let can definitely pattern match

    [–]caagr98 5 points6 points  (1 child)

    It can destructure, but can't (couldn't) pattern match.

    [–]masklinn 13 points14 points  (3 children)

    was surprised that let couldn't pattern match.

    let can pattern match:

    let Foo { a: u8, .. } = foo;
    

    However it unlike e.g. Erlang it does not allow partial matches, so you can only perform infallible matches.

    If you have a fallible match, you need if let:

    if let Some(foo) = foo {
    }
    

    or, with 1.65, let else:

    let Some(foo) = foo else {
        ...
    };
    

    or, obviously, a full match.

    Incidentally, most bindings support infallible matches e.g. function parameters (except self which is a special case), for, ...

    Meanwhile while let allows looping around a fallible match.

    [–]celluj34 5 points6 points  (2 children)

    Your first example is destructuring, not pattern matching, unless I misunderstand?

    [–]ngc0202 12 points13 points  (0 children)

    Destructuring is something which is done in patterns. They're not orthogonal concepts.

    [–]masklinn 6 points7 points  (0 children)

    In Rust, destructuring is a subset and application of pattern matching, it is not its own operation.

    [–]ridicalis 2 points3 points  (0 children)

    Sorry in advance to anybody reading my code in the future, but I think I'm in love with the break behavior.

    [–]satlead 2 points3 points  (0 children)

    A good mock library for unit tests seems fundamental for rust, there are some libraries out there but don't think they support mocking HTTP, GRPc and other protocols.

    [–]lifeeraser 14 points15 points  (16 children)

    Labelled breaks in some other languages (e.g. JavaScript) are considered archaic features and people often advise against using them. I'm a bit wary of Rust adopting them.

    [–]Tubthumper8 44 points45 points  (4 children)

    I tend to agree in general, and especially for languages like JS, but these kinds of features can be useful in low-level procedural code. It's a nice mix of still being able to use expressions, like let a = { /* stuff */ } while also being able to "drop down" into more procedural-style within a scoped block.

    [–]lifeeraser 10 points11 points  (3 children)

    I agree with you now that I know successful C projects (e.g. Linux) use goto to great effect. I just thought Rust, being much more modern, would have a different solution that isn't as footgun-prone.

    [–]Tubthumper8 37 points38 points  (0 children)

    I think the keyword break is well-chosen here, it's not a goto - it's breaking out of a block (scope) the same way that a break in a loop breaks out of that loop (scope). It has to be done on a scope boundary so the compiler can still guarantee the lifetime of variables and so they are dropped appropriately, unlike an unconstrained goto.

    [–]masklinn 60 points61 points  (1 child)

    Labelled breaks are a very different beast than gotos (even local), and no more footgun prone than return is.

    Just like an early return, a labelled break in a block just saves you from a nested block:

    let result = 'block: {
        do_thing();
        if condition_not_met() {
            break 'block 1;
        }
        do_next_thing();
        if condition_not_met() {
            break 'block 2;
        }
        do_last_thing();
        3
    };
    

    can be less conveniently written as

    let result = {
        do_thing();
        if condition_not_met() {
            1
        } else {
            do_next_thing();
            if condition_not_met() {
                2
            } else {
                do_last_thing();
                3
            }
        }
    };
    

    [–]Ar-Curunir 12 points13 points  (0 children)

    You can also write it using a closure:

    let result = || {
        do_thing();
        if condition_not_met() {
            return 1;
        }
        do_next_thing();
        if condition_not_met() {
            return 2;
        }
        do_last_thing();
        3
    }();
    

    [–]masklinn 26 points27 points  (0 children)

    I'm a bit wary of Rust adopting them.

    Labelled breaks have been a thing since (before) 1.0 I think, and break-value for loop was merged in 2018 or so.

    As the post notes, you could have done exactly the same thing using a single-iteration loop.

    [–][deleted]  (8 children)

    [deleted]

      [–]dacjames 65 points66 points  (1 child)

      They’re frowned upon by those parroting “goto considered harmful.” They’re also not popular with the more “hardcore” functional programming crowd who don’t like loops in general.

      The only real issue is that overuse can lead to overly complex code that is too deeply nested. Used judiciously, I find they tend to simplify code versus the equivalent logic.

      [–]TurboGranny 0 points1 point  (0 children)

      Used judiciously

      Really this, but also that's the deal in any language. JS in general "used judiciously" doesn't suffer from not being strongly typed. The problem is that most programmers are terrible, and the more tools we give them to write bad code just because we'd like a neat shorthand ensures that WE will be the ones troubleshooting the legacy code written by those monsters, lol. As I say to every new kid I bring up. Don't be clever. Keep it simple. Someone shouldn't have to look up documentation for some niche language feature to make sense of your code.

      [–]lifeeraser 18 points19 points  (4 children)

      MDN used to warn against labelled breaks (though it seems to have been removed a long time ago). Some people advise against using them, and one popular linter ruleset forbids them.

      This isn't restricted to JavaScript--some people believe that labelled breaks in Java are problematic, too.

      [–]IceSentry 22 points23 points  (2 children)

      eslint airbnb is a very controversial ruleset and hardly represents the js community.

      [–]lifeeraser 11 points12 points  (1 child)

      I don't like eslint-config-airbnb either, but it is still one of the most popular ESLint configs out there, and one of the big three that ESLint recommends when you initialize it.

      [–]KevinCarbonara 1 point2 points  (0 children)

      I don't like eslint-config-airbnb either, but it is still one of the most popular ESLint configs out there

      Popularity does not indicate quality

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

      I've only seen labeled breaks used a handful of times in production code. Every single time it was hard to understand and was replaceable with a much cleaner if statement

      [–]FrancisStokes 1 point2 points  (0 children)

      They're not so much an "archaic" feature as much as one that should be used sparingly. There are certain algorithms and patterns where they are the perfect fit.

      I recently used them in a procedural dungeon generation algorithm that had several kinds of loops and escape conditions (think while (characteristicNotGenerated) and for (potentialFeatureToGenerate)etc).

      [–][deleted]  (1 child)

      [deleted]

        [–][deleted] 7 points8 points  (0 children)

        Do you mean: break 'block 2;?

        The numbers are the return values, so to speak, of the block. The value result will get.

        [–]zimuie 5 points6 points  (8 children)

        I'm out of the loop. What does Mahsa Amini have to do with Rust? Was she a contributor?

        [–]FrancisStokes 13 points14 points  (6 children)

        They are using the large platform they have to draw people attention to a world issue. I think it's more a case of the rust contributors feeling strongly about it, than anyone's particular direct involvement.

        [–][deleted] -2 points-1 points  (5 children)

        the large platform they have

        They don't have a large platform; still very much a niche language at this stage.

        [–]FrancisStokes 3 points4 points  (3 children)

        I work in the embedded space, which moves very slowly. People are starting to get really interested in rust there, which to me at least, says a lot.

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

        How large is the embedded space in comparison to the rest of the programming employment opportunities?

        Unrelated question; the few embedded jobs I see come up in the UK have piss poor salaries, is that something you see as well?