4 ms·
> python expects you to share references to things. When you create a binding to a point in a contour, that binding refers to the same data as the original poin
by lalaithion 5y ago
> python expects you to share references to things. When you create a binding to a point in a contour, that binding refers to the same data as the original point, and modifying the binding modifies the collection.
Honestly, as someone who has written way more Python than Rust, this seems like bad API design regardless of language. This screams "it's impossible to take a font, make two separate modifications to it, and then work with those separate modifications at the same time", because deepcopying objects is usually very difficult.
- Blikkentrekker 5y agoWhy would deep copying be difficult? Even in Rust, the `Clone` trait produces a deep copy. Copying only up to a specific depth in a general way is what is difficult. I would argue that shallow copies, id est the depth being 1, are far more difficult to realize that copies of unbounded depth until a value o trait `Copy` is reached, id est a type that is purely encoded by the data it takes up on the stack and owns no further data and can thus be fully be `Clone`ed by copying it's stack bits.
- kzrdude 5y ago> Even in Rust, the `Clone` trait produces a deep copy. Clone is not really a deep copy. I like the description that says it is "deep enough to give you an object of identical/independent ownership (of what you started with), but no deeper". Example: Rc<String> when cloned only gives you another handle to the Rc, the string data is not duplicated (for this reason we don't call it a real deep copy). You get a new rc handle that is on equal footing with the old handle. There is plenty of Rust types that consist of tree-shaped ownership relations with no frills - in these cases clone and deep copy are identical. Let's say for example a HashMap<i32, String>.
- oconnor663 5y agoI think every "deep" copy ultimately has some limitation like this. Even in Python, an object can implement __deepcopy__() to prevent copy.deepcopy() from "copying too much", and things like file objects, classes, and modules get special treatment.
- kzrdude 5y agoI found that in Rust, you don't have to think about the distinction so much - not like you have to in for example Python and C# (pervasive/transparent object reference languages). In Rust it's predictable and mostly obvious which data is shared ownership or shared, and which is not. We don't need to talk about deep vs shallow copy semantics much in Rust. :)
- Blikkentrekker 5y agoYou are correct; my original definition that only went till `copy` was encountered is only the most common case of your more general definition. The principles are still quite similar and it still seems easier to implement to me than shallow copies as a generic trait. I don't know how I would fit shallow copies into Rust's trait system.
- kzrdude 5y agoClone seems to be the shallowest kind copy permitted by the type and ownership system.
- Blikkentrekker 5y agoWhich is why I find deep copies to be easier to implement than shallow. It is of course possible to express a shallower copy, but not in the same generic way as a universal trait, that I know of, in any case.
- wk_end 5y agoIt's not intrinsically difficult, but in Python it's arguably un-Pythonic and somewhat awkward. Like, sharing references has built-in syntax that's taught to most Python programmers within their first hour with the language, whereas copying - without writing a tedious manual implementation - requires importing the `copy` module [0], which outlines a number of pitfalls, quirks, and caveats with the process, and that I've literally never used so far as I can recall in a decade of professional Python development. [0] https://docs.python.org/3/library/copy.html https://docs.python.org/3/library/copy.html
- adrianN 5y agoYou have to pass around the whole object you want to copy for that to work. For example in a compiler, if you function only sees an AstNode, it can't make a deep copy of the whole AST easily. You also have to be careful with things like objects that contain refcounted pointers.
- xapata 5y agoPassing pointers around is a pretty common pattern. It avoids allocating memory and copying data unnecessarily. Seemed like a decent choice in the late 80's and early 90's, probably all the way up through mid-2000's. It's still fine as the default for many situations.
- TheCoelacanth 5y ago> It avoids allocating memory and copying data unnecessarily My experience is the exact opposite. Pervasive shared mutability leads to developers making lots of unnecessary copies out of fear that some other part of the code will change values out from under them.
- skrtskrt 5y agoMaybe it's just the kind of work I have done (web services, where objects don't live longer than a request), but I have never had a problem of "some other part of the code changing values out from under me" in Python or Go.
- xapata 5y agoI go with the "we're all adults here" philosophy and trust my colleagues not to inappropriately mutate the things they receive. It's worked well enough so far. I understand why it might not work so well for other people.
- Orou 5y agoNaive question: in Python, isn't this solved by .copy and .deepcopy if you really have to get a copy of an object rather than create a new instance of something? I'm curious where the bad API design is, unless you're saying that Python's assignment behavior itself is bad and everything should be immutable by default.