Things like:
slices.Sort(s) // correct
slices.Compact(s) // incorrect
slices.Delete(s, ...) // incorrect
s := slices.Delete(s, ...) // incorrect if 's' is referenced again in the outer scope
s = slices.Delete(s, ...) // correct
All of those are solved by having functions like 'slices.Sort' take a '&mut' reference in rust speak, and having 'slices.Compact' and 'Delete' take an owned slice, and return a new owned slice.[1] Especially given that the capacity is preserved by default, which contributes to the current confusion. See my older comment: >>39112735
[2] Originally "...are different" but edited for clarity.
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.
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).
If Delete or Compact are only available there and it’s modified in place, the problems don’t arise in the first place.
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.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()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.
Even without going the whole 9 yards of the Rust memory model, something like the views in C++, or the java {Array,Linked}Lists containing references to objects and thus being resistant to re-alloc, or even plainly immutable list like in OCaml are all working and simple enough solutions to the issue.
I still can't wrap my mind around how supposedly experienced people like Pike & co. may have designed slices, then went ‶yep, that makes perfect sense for a robust, basic, widely used data structure to create our language around″, outside of a few toy programs.
Agreed, although conversely if we could start Rust development all over again I'd probably argue that must_use on functions (as opposed to on types) should probably be the default behavior. (These days it basically is the default behavior for functions in the standard library.) Though Rust didn't gain the ability to annotate functions with must_use until 1.27. Switching the default could perhaps be a good candidate for an Edition.
By having this weird API on slices, they force you to be explicit on allocating new memory.
s := []int{}
for i := range 29754 {
s = append(s, i)
}
do you see explicit allocation of new memory? As far as I'm concerned that's not in any way more explicit than e.g. var s = new List();
foreach(var i in Enumerable.Range(0, 29754)) {
s.Add(i);
}I also fail to see how this would translate to
> make programmers mindful of the cost of allocating new space on array operations.
anyway. `append` is not taking in an allocator, reporting allocation failures, or reporting reallocations here. Like proper vectors, it's silently reallocating (and over-allocating) as needed.
Not at all. With e.g. `append` being a “maybe I'll allocate, maybe not, take a guess” kind of method, it's basically just like Java's `List.add` with extra steps and much worse egonomics.