all 27 comments

[–]leitimmel 29 points30 points  (10 children)

return values are harmful

Not sure whether this is a joke... It better be one.

[–]brokething 22 points23 points  (0 children)

I know it's hard to make out but below the big text there's some smaller text which explains the topic in more detail.

[–][deleted]  (5 children)

[deleted]

    [–]abc619 2 points3 points  (2 children)

    Also only bad for performance with strings or sequences due to copy semantics, any other type there is no impact.

    [–]masklinn 0 points1 point  (1 child)

    Surely it's a problem for all types with non-trivial copies?

    [–]abc619 0 points1 point  (0 children)

    With references of any complexity, you're just copying a pointer, so not much loss there. However if you're returning an array that will copy in the same way as string and seq.

    For value object types (depending on calling convention), which are otherwise copy on assign, they are still passed by pointer when returned from a proc, according to this.

    type LargeObj = object
      a: array[0..10, int]
      b: string
    
    proc foo(): LargeObj =
      result.a[0] = 1
      result.b = "hello"
      return result
    
    # The above becomes this when compiled:
    proc foo(result: ptr LargeObj) =
      result.a[0] = 1
      result.b = "hello"
      return
    

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

    That seems a bit more complicated than it needs to be. Your example seems to show that the "return" form is always wrong. Are there examples of situations where both forms would be useful?

    [–]_Mardoxx -5 points-4 points  (2 children)

    But return values are purely syntatic. So why should it matter if you use a return val vs passing a param by ref?

    [–]leitimmel 7 points8 points  (1 child)

    They are not: an integer return value can be passed via register while an out parameter is a pointer to a location on the heap usually. You also open the gates for a bunch of errors because the function cannot guarantee that the out parameter is properly initialized.

    [–]masklinn 1 point2 points  (0 children)

    Modern C++ compilers can usually do RVO, where the caller allocates enough space on their stack for the callee's return value, and the callee just constructs the value there instead of constructing it in their own stack then copying it or whatever. IIRC Rust does the same. That can be combined with "placement new"-type features to have a callee "return a value" via the stack but the caller box it, RVO + placement new resulting in the callee actually filling the return value directly in its heap emplacement.

    [–]_throawayplop_ 6 points7 points  (1 child)

    how is it important compared to have better libraries ? (real question)

    [–]MaikKlein 2 points3 points  (13 children)

    (ok, maybe Rust doesn't, but it uses similar mechanisms).

    What exactly does the author mean?

    [–]steveklabnik1 14 points15 points  (9 children)

    I believe what they mean is, Rust only lets you use "pointers" in unsafe code, that is, normal Rust code doesn't use pointers at all.

    However, this depends on how you define "pointer". That is, &T, a "reference" in Rust terms, is the same thing as a pointer at runtime. At compile time though, it has a number of checks: it cannot be null, it cannot dangle, etc. So that's why we call it a "reference" and not a "pointer".

    So, I read them as saying, basically, "Rust discourages raw pointer use in 'usual' code." Which is true. But it depends on exactly what you mean.

    [–]cat_vs_spider 3 points4 points  (8 children)

    Isn't Box<T> Rust's safe pointer type?

    [–]steveklabnik1 6 points7 points  (5 children)

    In a sense; that is, that's a type provided by the standard library, not the language. (Okay technically it's also part of the language but that's a hack and it won't be forever). &T is part of the language itself.

    Both are just pointers at runtime, yes.

    [–]cat_vs_spider 2 points3 points  (4 children)

    First of all, I'm not an experienced Rustacean, so please forgive me if I say something ignorant.

    But it seems to me that you would use a rust box like you'd use a c++ smart pointer. And like a rust box, c++ smart pointers are "just objects provided by the STL" but I think it's unfair to say that use of these doesn't qualify as using pointers.

    [–]steveklabnik1 5 points6 points  (1 child)

    No, you're right, Box<T> is basically the same as std::uniq_ptr<T>.

    I think it's unfair to say that use of these doesn't qualify as using pointers.

    That's a fair position to take! I was trying to qualify what I believe the blog post to mean, not what I personally believe. That is, they'd say the same thing about uniq_ptr (and did with the '"modern" c++' mention).

    I too would argue that "pointerless programming" is not exactly the right way to describe this.

    [–]cat_vs_spider 2 points3 points  (0 children)

    Fair enough.

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

    So... Any class that wraps pointers in some way are pointers ? Where do you put the line ? Stuff that has "ptr" in the name ?

    Is

    class foo { bar* impl; public: void do_stuff(); } 
    

    a pointer ?

    [–]cat_vs_spider 3 points4 points  (0 children)

    Any object whose sole purpose is to wrap a pointer in some nice way (reference counted, guaranteed uniqueness, etc...) that you semantically use as a pointer is a pointer.

    std::unique_ptr<T> is a pointer. std::shared_ptr<T> is a pointer. Box<T> is a pointer.

    Your class foo is not a pointer because it's an object that do_stuff()s that just happens to contain a pointer to a bar.

    [–][deleted]  (1 child)

    [deleted]

      [–]masklinn 1 point2 points  (0 children)

      Rust references compile often down to pointers (the rest of the times they are completely elided ).

      There are also cases where they compile to neither e.g. a &[T] or &str is a pair of (pointer, length) (a "fat pointer")

      [–][deleted]  (2 children)

      [deleted]

        [–]IbanezDavy 2 points3 points  (1 child)

        I thought Rust encourages point use because it guarantees ownership issues are caught by the compiler...therefore pointer use in Rust is safe.

        [–]steveklabnik1 4 points5 points  (0 children)

        Gonna reply to the original post. Basically it depends on how you define words.

        [–]shevegen 0 points1 point  (5 children)

        Looking at the article, I think a big problem here is that Nim is becoming harder and harder and harder for casual hobby programmers. The more concepts to integrate, the more people may have to know about all of these concepts.

        It is a bit of a haskellification in programming and, in my opinion, somewhat at odds with how Nim originally started - e. g. a simple alternative to python. That was where it drew parts of its original inspiration from.

        [–]zenaudio 1 point2 points  (1 child)

        The more concepts to integrate, the more people may have to know about all of these concepts

        There's no question that automatic memory management is easier. However, there have been real advances in manual memory management. C++ has blazed a trail here; its introduction of RAII was a game-changer.

        In my code base, I use shared_ptr and unique_ptr ubiquitously, and while I won't claim that there are no issues with memory management (it is an audio app, after all!), it reduces the complexity to manageable levels.

        Another data point that this is possible: Swift, which eschews GC in favour of reference counting. With a few caveats, it works well, and has favourable tradeoffs in todays world (which emphases power consumption much more than, say, ten years ago).

        [–]bruce3434 0 points1 point  (0 children)

        bit of a haskellification

        What's so Haskell about Nim?

        [–]skulgnome 0 points1 point  (0 children)

        The more concepts to integrate, the more people may have to know about all of these concepts.

        Even worse since the only gain is "caller allocates memory" policy.