Hacker News new | past | comments | ask | show | jobs | submit login

> This is an insidious pitfall that may earn Rust the "slow" label, akin to what happened to Common Lisp.

This doesn't match my experience at all with Rust, for what it's worth. Not having copy constructors makes up for any difference in that regard: in C++ it's way too easy to accidentally deep copy a vector and the entire object graph underneath it--all it takes is "auto a = b" as opposed to "auto& a = b"--with very bad performance consequences. Rust, on the other hand, doesn't let you deep copy unless you explicitly ask it to.




When I went back to C++ after learning Rust and used a vector again, I realized this and it hit me like a brick. I used to program in C++ before, so it wasn't like I didn't know this, but it was something rather unremarkable to me. Then I go back to C++ and throw vectors around willy-nilly and stuff works. Huh, I thought, this feels strange, I wonder how C++ accomplishes this without a garbage collector or ownership. Then it hit me -- it deep copies All The Things. Again, I already knew this, but I hadn't ever thought about it much. But the change in perspective made it stick out like a sore thumb.


The Qt C++ library does some neat tricks to avoid copying. For example its vector class (like nearly all of its container/value classes) overloads the "=" operator to perform shallow copies. So even though default behavior in C++ is to deep copy, it's possible to override.

Qt even takes it further by providing automatic copy-on-write. Methods that read data operate on the shared copy. Methods that change data cause the object to deep copy and detach first. This allows writing programs using values rather than references, while retaining the performance of references.


I got so worried about accidentally deep-copying stuff that I ended up with an ugly C++ hack:

    C(const C &) = delete;
    C &operator=(const C &) = delete;
    C(C &&) noexcept = default;
    C &operator=(C &&) noexcept = default;

    struct MakeCopy {
        const C &src;
        MakeCopy(const C &src) : src(src) { }
    };

    MakeCopy make_copy() const { return *this; }
    C(MakeCopy src) : field1(src.src.field1), (blah blah) { }
Then, when you really want to copy stuff, you have to say "a = b.make_copy();".

This isn't really clean, because it's too easy to add a field to C and then forget to add it to the "manual copy constructor", but so far I found it good enough for me.


In Rust, if you want a deep copy you (IIRC) implement the Clone trait, which allows you to explicitly clone everything. Many collections in the standard library already implement this, so you get it by default :).

Edit: I should point out that deep copies can only be explicit in Rust -- there's no implicit deep copy AFAIK.


There is no implicit deep copy, Clone is usually how a deep copy is implemented, but not all Clones are deep copies. Rc, for example, bumps a reference count on Clone.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: