← Back to context

Comment by makapuf

2 years ago

This would not allow the previous errors to be checked by the compiler since the main thing you're relying on is the name. Nothing prevents you to call deleted(mutable) and discard the result apart from the name.

Indeed. Though, Rust does have a way to mark a function such that any caller that implicitly discards its return value gets a compiler warning. This feature largely solves the problem you’re talking about. But it’s orthogonal to Rust’s borrowing system or mutable versus immutable distinction.

That said, I’d also point out that, while you can more or less replicate the Go example with Rust slices, in Rust it would be more idiomatic to pass around a Vec (or a mutable reference to a Vec) if a callee needs to do something like change the length. And you can’t resize a Vec if there are other references to its contents.

  • `#[must_use]`. I really don't know why Rust made that error.

    • I think it is notable that both `try!` (`?` today) and `#[must_use]` (originally restricted to `Result`, then made available in general later) appeared in the same release (0.10). In the other words, `#[must_use]` was strongly tied to `Result` back then. While we can put `#[must_use]` to any type now, the set of types that absolutely have to be `#[must_use]` remains relatively small, with a major addition being iterators and futures. Once they have been covered, any additional value from adding `#[must_use]` is not large enough to make it default, I think.

      2 replies →

> Nothing prevents you to call deleted(mutable) and discard the result

The Go compiler generates an error when you are (silently) ignoring the return value of any function. Or, to put it in other words, every compiler which does allow to (silently) ignore the return value of a function, should not be used at all (C++ has at least `[[nodiscard]]` since 17 and C with C23 - which is "too little and too late", as always).

  • > The Go compiler generates an error when you are (silently) ignoring the return value of any function.

    It does not. You can actually infer that from TFA listing cases as problematic which would be caught by such a compilation error, and confirming it by just compiling them:

        a := []int{}
        // compiler says nothing
        slices.Delete(a, 0, 0)
    

    The builtins are special cased to error if their return value is ignored (except for copy and recover).

    > C++ has at least `[[nodiscard]]` since 17 and C with C23 - which is "too little and too late", as always

    You can't even mark your own functions or types as nodiscard in Go. You need third-party tooling even just to ensure you're not unwittingly ignoring error results:

        f, err := os.Create("/tmp/filename")
        if err == nil {
            return
        }
        // compiler doesn't say anything even though f.Close returns error
        f.Close()

    • Sorry, yes, you are of course right. That's the linter which complains, not the compiler.

      I have to say that I don't understand the rationale of a compiler that errors on unused variables but lets the user silently ignore function return values. As a solution to explicitly ignore return values already exists in the language.

While that's a valid concern, it is an orthogoal issue as it can be similarly replicated in Rust as well. Rust references always track mutability but we can sidestep that by using `std::borrow::Cow`:

    fn compacted<T: ...>(input: Cow<'_, [T]>) -> Cow<'_, [T]> { ... }

Then it is clear that, for example, `compacted(vec![...].into());` as a statement will exhibit the same behavior because `Cow` doesn't have `#[must_use]`. Rust avoids this issue mainly by encouraging explicitly mutable or immutable values by default, and at this point the language should have substantially altered that Go can do the same.