zlacker

[return to "Go(lang): Robust generic functions on slices"]
1. TheDon+IZ2[view] [source] 2024-02-24 05:22:50
>>signa1+(OP)
The problems with the API they point out are almost all things that rust's ownership system was built to solve.

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.
◧◩
2. lifthr+o13[view] [source] 2024-02-24 05:51:17
>>TheDon+IZ2
You don't even need a notion of ownership. A distinction between an immutable and mutable slice should be enough, because an immutable slice can never be changed which implies that its excess capacity (if any) can't be exploited for optimization.
◧◩◪
3. tsimio+I23[view] [source] 2024-02-24 06:10:14
>>lifthr+o13
None of these functions would apply to an immutable slice, so how is it related?
◧◩◪◨
4. lifthr+k33[view] [source] 2024-02-24 06:19:51
>>tsimio+I23
If immutable and mutable slices are differently typed [2], it is natural to define two functions (say, `slices.Compact` vs. `slices.Compacted`) to handle each type, like Python `list.sort` vs. `sorted`. It should be natural to expect `slices.Compacted` to never alter its input, and any attempt to use a mutable version will be very explicit except for slicing [1].

[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.

◧◩◪◨⬒
5. makapu+b53[view] [source] 2024-02-24 06:46:57
>>lifthr+k33
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.
◧◩◪◨⬒⬓
6. Releas+983[view] [source] 2024-02-24 07:29:59
>>makapu+b53
> 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).

◧◩◪◨⬒⬓⬔
7. maskli+ji3[view] [source] 2024-02-24 10:11:58
>>Releas+983
> 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()
◧◩◪◨⬒⬓⬔⧯
8. Releas+Lj3[view] [source] 2024-02-24 10:34:56
>>maskli+ji3
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.

[go to top]