Programming Languages - Assignment Semantics
3 kinds of assignment semantics:
- share semantics
- copy semantics
- move semantics
E.g. for this pseudocode
v1 = {1, 2, 3}
v2 = v1
- Share semantics (e.g. Java): the header of v1 is copied to the header of v2; v1 and v2 both refer to the same heap buffer.
- Copy semantics (e.g. C++): another heap buffer is allocated, content of v1 is copied to v2; v1 and v2 refer to distinct objects that happen to be the same initially.
- Move semantics (e.g. Rust and C++
std::move()
): the header of v1 is copied to the header of v2; v1 cannot be used after the assignment.
By language:
Share | Copy | Move | |
---|---|---|---|
Java | Yes | ||
C++ | Default | std::move |
|
Rust | .clone() |
Default | |
Go | Default |
Rust
Default move (however for primitive numbers, static strings, and references, Rust use copy semantics); to copy: let v2 = v1.clone()
.
copyable vs clonable:
- copyable (implicitly cloneable): objects that cannot own anything and are easy and cheap to copy. E.g. numbers.
- cloneable but not copyable: objects that may own some heap objects and not cheap to be copied. E.g. collections.
- not cloneable: objects that own external resources like a file handle or a GUI window handle.
C++
Default copy; to move: std::move(v1)
.
Go
Copy semantics. Does not support move semantics.
v1 := [3]int{1, 2, 3}
v2 := v1
v1[0] = 1000
fmt.Println(v1)
// [1000 2 3]
fmt.Println(v2)
// [1 2 3]
Note if we pass a slice to a func, slice header is passed by value, so the function receives a copy of the header, but the header points to the same underlying array.
func modify(s []int) {
s[0] = 100
}
func main() {
// s is a slice
s := []int{1, 2, 3}
modify(s)
fmt.Println(s)
// [100 2 3]
}
If we pass the array, it will be a copy inside the func:
func modify(a [3]int) {
a[0] = 100
}
func main() {
// a is an array
a := [3]int{1, 2, 3}
modify(a)
fmt.Println(a)
// [1 2 3]
}
Deallocation
- In C / C++, heap objects used to be deallocated explicitly by
free
,delete
(not any more if you use smart pointers). - In Java / C#, heap objects are not immediately deallocated, they are cleaned up by garbage collector.
- In Rust, or modern C++, heap objects are deallocated when the owner is unreachable. (Rust:
Box<T>
; C++:std::unique_ptr<T>
)
language | deterministic? | explicit? |
---|---|---|
C / C++ free , delete |
deterministic | explicit |
Java / C# | nondeterministic | implicit |
Rust / C++ smart pointer | deterministic | implicit |