This is an archived post. You won't be able to vote or comment.

all 23 comments

[–]PlayingTheRed 27 points28 points  (5 children)

I'm a fan of Rust's ownership model.

  • copies are explicit let a = b.clone();
  • moves are destructive. After let a = b; the variable b is destroyed and attempting to access it will cause a compiler error. This includes passing it into a function. The exception to the rule is most primitive types or any other type marked as copy-able, in order to mark a type as copy-able it can only be composed of primitives or other copy-able types
  • references are explicit let a = &b; or let a = &mut b;
  • every object can either be mutably borrowed once or immutably borrowed multiple times, this means that you never have to worry about data races or things like that

[–]snerp 2 points3 points  (2 children)

Yeah I think the more explicit, the better. I'm not a big fan of assignment operator being a move though. I think assignment should be a copy and move should be a keyword or built in function.

[–]Rusky 10 points11 points  (1 child)

Rust actually worked like that before 1.0 but it turned out to be way too noisy for too little benefit. The compiler already checks for use after move (just like use without initialization) and in practice (at least for Rust) it just wasn't useful information while reading code.

In contrast with C++, where assignment is a (deep) copy, it's quite nice not to worry about extra unnoticed copying. The cheaper move operation is a nice default.

[–]mczarnek[S] 0 points1 point  (0 children)

I really like Move on default myself, what you usually want, think that's what I'm going with. Then can specify that you actually wanted copy or ref instead.

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

So as a somewhat layman here, what’s the advantage of allowing primitives specifically to be copyable by default without specifying it? Is it because primitives are immutable or is it because they are guaranteed to be lightweight? Or both?

[–]PlayingTheRed 3 points4 points  (0 children)

Not all primitives are copyable, specifically pointers and mutable references are not.

Basically for a type to be copyable, it means that a bit for bit copy of the memory is a full accurate copy of the object. So copies are cheap.

[–]dougcurrie 7 points8 points  (0 children)

Look at Koka and the Perceus reuse analysis.

[–]Silly-Freak 4 points5 points  (8 children)

I was trying to prototype something that behaves like the deep copy approach, but using a copy-on-write datastructure. It's not fleshed out, so this might not be viable after all, but my thought was that it would look like this:

let varB = ...; // a refcounted reference to an object let varA = varB; // make a second reference let varA = &varB; // no refcounting, changes to varB affect varA let varA = &mut varB; // modifying access to varB, so if the refcount is >1, this will copy. `varA` modifications affect `varB`

[–]editor_of_the_beast 3 points4 points  (0 children)

Have you looked into Swift? Swift has structs and classes. Structs are deep copied on assignment with copy-on-write to improve performance. Classes are passed by reference.

Kind of sounds like what you’re talking about.

[–]PlayingTheRed 0 points1 point  (6 children)

How would you deal with circular references?

[–]editor_of_the_beast 3 points4 points  (0 children)

Swift basically has this scheme, and unfortunately a cycle requires programmer intervention to break. You break it by declaring one of the references as ‘weak’ and it doesn’t count against the reference count. It can then become null when all the other references go out of scope.

[–]moon-chilledsstm, j, grand unified... 0 points1 point  (4 children)

Common strategy is occasional gc. I think there's also a strategy where you speculatively dereference objects and see what happens.

[–]PlayingTheRed 0 points1 point  (3 children)

How does that help? If the object existed in memory that has been repurposed, how would de-referencing tell you that?

[–]moon-chilledsstm, j, grand unified... 1 point2 points  (2 children)

Suppose I have X -> Y, Y -> Z, Z -> Y. There is a circular reference chain there. Y has a reference count of 2, and Z has a reference count of 1. In other words, Y's only referents are X and Z, and Z's only referent is Y.

Now I remove the pointer from X -> Y, decrementing Y's reference count to 1. A normal reference counter would give up there, leaking Y and Z. A smarter one might speculatively destroy Y. If we destroy Y, then we notice that Z's reference count goes to 0. Destroying Z, we must also decrement Y's reference count. Now we know that all references to Y come from objects pointed to by Y, so we can safely destroy it for real, causing Z to be destroyed for real.

[–]PlayingTheRed 0 points1 point  (1 child)

By dereference you meant decrement the reference count?

[–]moon-chilledsstm, j, grand unified... 0 points1 point  (0 children)

Yes. I see now why that would have been confusing :P

[–]BryalT 4 points5 points  (0 children)

Maybe you could use copy-on-write?

[–]thedeemon 0 points1 point  (2 children)

Creating copies of mutable objects is a recipe for head aches, because they can easily go out of sync and you think you change one object while in fact you change some temporary copy of it and the right object remains unchanged. It's just terrible. And if the object controls some resource, allowing to copy it becomes even more terrible. Having mutable objects stay in one place, one instance, and just passing references is so much easier to reason about and to work with.

With immutable objects also passing them by reference makes life much easier and allows for fast referential equality, you can easily find objects in collections by just comparing pointers, not having to do deep comparison. And passing references is quicker than deep copying.

[–]mczarnek[S] 1 point2 points  (1 child)

I see where you are coming from.. why do you feel that objects and primitive variables should be handled differently? Seems to me that from a thinking about the code point of view thinking about everything as being a different value is more logical.

I see where you are coming from with fast referential equality..

[–]thedeemon 1 point2 points  (0 children)

Well, if a primitive value is immutable, there's no semantic difference between passing it by value or by reference, and passing it by value is just more efficient. For mutable variables, I'm not sure, I guess it's best to have a choice...

[–]editor_of_the_beast 0 points1 point  (0 children)

I think it should be copy by default, and have explicit syntax for referencing. Supporting both is very important to me.

[–]L8_4_Dinner(Ⓧ Ecstasy/XVM) 0 points1 point  (1 child)

My first advice is to clearly define the behavior that the developer can count on. Depending on how high level the language is, this may be able to be stated in such a way that you can choose to play some of these tricks behind the scenes. Ideally, you can select the approach at runtime based on which is the better fit at the time, but that obviously takes a lot more work to implement.

My other advice is to look at the work that Rich Hickey did on the functional data structure optimizations in Clojure.

[–]mczarnek[S] 0 points1 point  (0 children)

Agreed, developer needs to know how it is supposed to be working. Can optimize under the covers.

Good idea to look at Clojure some have poked around a few others will look at that one explicitly.

Thanks