Study notes, benchmarking cpp to understand the concept of rust ownership and borrowing, and mentioning the more specific slice (DST) by the way

Ownership

Each value in rust has an owner variable and can only have one owner at the same time. When the value’s owner variable goes out of scope, the value’s memory is freed.

The following code String has moved ownership from s1 to s2 and a Move has occurred, at which point it is illegal to access s1 again. Reference

1
2
let s1 = String::from("hello");
let s2 = s1;

The following code i32 ownership is not transferred from x to y, but rather y copies x and a Copy occurs

1
2
let x: i32 = 1;
let y = x;

Move is also essentially a shallow copy: for example, the internal implementation of String has a pointer to the saved string, and ownership is transferred, but in fact only the value of this pointer is copied, not the string. At this point both s1 and s2 memory spaces hold the pointer address, and because of the ownership, the compiler ensures that accessing s1 is illegal, so s1 can’t do anything, even though it still points to the string, ensuring safety.

About memory release: Since the release occurs only after the owner’s life is over, ownership guarantees that there is only one owner at the same time, so the address where the string is located is not freed twice by double free .

Here I would like to benchmark cpp: cpp implements similar efficient transfers using right-valued references and move constructors. The pointer to s1 is stolen from the move constructor of s2, and then the pointer to s1 is pointed to the address of an empty string or marked as invalid. s1 is passed as a right-valued reference, which is semantically a will-dead value, so the internal structure of s1 can be modified. However, cpp has no concept of ownership and the compiler will not prevent you from continuing to access s1. This is not safe!

rust’s case for Move and case for Copy depends on whether the type implements the Copy Trait. i32 above is already small, and there’s nothing to make a shallow copy of (just 4 bytes to toss around), so i32 is copy semantics.

Almost all of the rust basic types implement the Copy Trait.

1
2
3
pub trait Copy: Clone {
// Empty. 只是个Marker
}

For tuples and arrays, if the elements all implement Copy, a copy is also passed. For complex types, if a part of a type implements Drop Trait, then the type cannot implement Copy; complex types can also implement Copy if the components all implement Copy.

Ownership transfer can occur for assignments, passes, and function returns.

references & borrowing

In the following code b does not take ownership, but gets a reference to a via &.

1
2
let a = String::from("123");
let b = &a;

b is a reference to a can also be described as b borrowed from a, rust references to the underlying can be marked as pointers in other languages, but rust references with life cycle and borrowing checks so very safe. A pointer in cpp, for example, only records a memory address, which is no different from an integer, and can be saved and taken anywhere, making it prone to memory leaks. The rust compiler will ensure that the lifetime of a reference does not exceed the lifetime of the value it points to.

References are divided into immutable referencesimmutable references and variable referencesmutable references , get a variable reference using let b = &mut a, provided that a is variable to get a variable reference, the relationship between variable and immutable references is similar to a read-write lock: 1.

  1. there can be more than one immutable reference (read lock)
  2. variable references and immutable references can not exist at the same time (read, write lock mutually exclusive)
  3. can only have a variable reference (write lock)

slice

Slices are special and are used to reference sequences of consecutive elements in an array.

  1. string slices - &str
  • let s = String::from("hello world"); let hello = &s[0..5];
  • let s: &str = "xxx"; let s2: &str = &s[...] ;
  • The special point of string slicing is that the range can only take valid utf8 character boundaries
  1. array slicing - &[T]
  • let a = [1, 2, 3, 4, 5]; let slice = &a[1..3];

The slice uses [start..end] to determine the reference range, and the interval is left-closed and right-open [start,end). The range can also be abbreviated as [. .2] , [3...] , [...] , omitted to indicate that bounds are taken.

The slice is a fat pointer that will hold a pointer to the target set, with the reference range.

What makes slices special is the need to talk about rust’s Dynamic Sized TypeDynamic Sized Type,DST , where DST denotes a type whose size cannot be fetched at compile time.

Starting with arrays, the type of an array is represented as [T; N] , T is the element type and N is the number of elements, so the size of an array is compile-time determinable, and arrays are not DST. note that &[i32; 3] is a normal array reference, while &[i32] is an array slice.

**The type [T] represents a slice consisting of T, the length of which is indeterminate at compile time (DST). The compiler cannot allocate space for a type of indeterminate size, so it cannot declare variables of type DST either, but can only refer to them with the fat pointer &[T].

The size of &[T] is fixed and contains space for storing the address and length of the data so that length information can be obtained at runtime. For example, to make a slice [1..n], the size of n is not available during compilation, so the value of n can only be calculated during runtime and then initialized with a fat pointer to complete the reference.

The string slice str is also DST, corresponding to the fat pointer &str, which can be interpreted as a special form of [T], mainly used to represent utf8 strings.

In addition to slicing, dyn Trait (Trait object) is also DST, and the corresponding fat pointer is &dyn Trait. (As long as it is a DST type, it is not possible to declare variables of the corresponding type