logo

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