Marcin Jahn | Dev Notebook
  • Home
  • Programming
  • Technologies
  • Projects
  • About
  • Home
  • Programming
  • Technologies
  • Projects
  • About
  • An icon of the Core section Core
    • Programs Execution
    • Stack and Heap
    • Asynchronous Programming
      • Overview
      • Event Queues
      • Fibers
      • Stackless Coroutines
  • An icon of the .NET section .NET
    • HTTPClient
    • Async
      • How Async Works
      • TAP Tips
    • Equality
    • Comparisons
    • Enumerables
    • Unit Tests
    • Generic Host
    • Logging
    • Configuration
    • Records
    • Nullability
    • Garbage Collector
    • IL and Allocations
    • gRPC
    • Source Generators
    • Platform Invoke
    • ASP.NET Core
      • Overview
      • Middleware
      • Razor Pages
      • Routing in Razor Pages
      • Web APIs
      • Filters
      • Identity
      • Validation
      • Tips
    • Entity Framework Core
      • Overview
      • Testing
      • Tips
  • An icon of the Angular section Angular
    • Overview
    • Components
    • Directives
    • Services and DI
    • Routing
    • Observables (RxJS)
    • Forms
    • Pipes
    • HTTP
    • Modules
    • NgRx
    • Angular Universal
    • Tips
    • Standalone Components
  • An icon of the JavaScript section JavaScript
    • OOP
    • JavaScript - The Weird Parts
    • JS Functions
    • ES Modules
    • Node.js
    • Axios Tips
    • TypeScript
      • TypeScript Environment Setup
      • TypeScript Tips
    • React
      • React Routing
      • MobX
    • Advanced Vue.js Features
  • An icon of the Rust section Rust
    • Overview
    • Cargo
    • Basics
    • Ownership
    • Structures
    • Enums
    • Organization
    • Collections
    • Error Handling
    • Generics
    • Traits
    • Lifetimes
    • Closures
    • Raw Pointers
    • Smart Pointers
    • Concurrency
    • Testing
    • Tips
  • An icon of the C/C++ section C/C++
    • Compilation
    • Structures
    • OOP in C
    • Pointers
    • Strings
    • Dynamic Memory
    • argc and argv Visualization
  • An icon of the GTK section GTK
    • Overview
    • GObject
    • GJS
  • An icon of the CSS section CSS
    • Responsive Design
    • CSS Tips
    • CSS Pixel
  • An icon of the Unity section Unity
    • Unity
  • An icon of the Functional Programming section Functional Programming
    • Fundamentals of Functional Programming
    • .NET Functional Features
    • Signatures
    • Function Composition
    • Error Handling
    • Partial Application
    • Modularity
    • Category Theory
      • Overview
      • Monoid
      • Other Magmas
      • Functors
  • An icon of the Algorithms section Algorithms
    • Big O Notation
    • Array
    • Linked List
    • Queue
    • Hash Table and Set
    • Tree
    • Sorting
    • Searching
  • An icon of the Architecture section Architecture
    • What is architecture?
    • Domain-Driven Design
    • ASP.NET Core Projects
  • An icon of the Core section Core
    • Programs Execution
    • Stack and Heap
    • Asynchronous Programming
      • Overview
      • Event Queues
      • Fibers
      • Stackless Coroutines
  • An icon of the .NET section .NET
    • HTTPClient
    • Async
      • How Async Works
      • TAP Tips
    • Equality
    • Comparisons
    • Enumerables
    • Unit Tests
    • Generic Host
    • Logging
    • Configuration
    • Records
    • Nullability
    • Garbage Collector
    • IL and Allocations
    • gRPC
    • Source Generators
    • Platform Invoke
    • ASP.NET Core
      • Overview
      • Middleware
      • Razor Pages
      • Routing in Razor Pages
      • Web APIs
      • Filters
      • Identity
      • Validation
      • Tips
    • Entity Framework Core
      • Overview
      • Testing
      • Tips
  • An icon of the Angular section Angular
    • Overview
    • Components
    • Directives
    • Services and DI
    • Routing
    • Observables (RxJS)
    • Forms
    • Pipes
    • HTTP
    • Modules
    • NgRx
    • Angular Universal
    • Tips
    • Standalone Components
  • An icon of the JavaScript section JavaScript
    • OOP
    • JavaScript - The Weird Parts
    • JS Functions
    • ES Modules
    • Node.js
    • Axios Tips
    • TypeScript
      • TypeScript Environment Setup
      • TypeScript Tips
    • React
      • React Routing
      • MobX
    • Advanced Vue.js Features
  • An icon of the Rust section Rust
    • Overview
    • Cargo
    • Basics
    • Ownership
    • Structures
    • Enums
    • Organization
    • Collections
    • Error Handling
    • Generics
    • Traits
    • Lifetimes
    • Closures
    • Raw Pointers
    • Smart Pointers
    • Concurrency
    • Testing
    • Tips
  • An icon of the C/C++ section C/C++
    • Compilation
    • Structures
    • OOP in C
    • Pointers
    • Strings
    • Dynamic Memory
    • argc and argv Visualization
  • An icon of the GTK section GTK
    • Overview
    • GObject
    • GJS
  • An icon of the CSS section CSS
    • Responsive Design
    • CSS Tips
    • CSS Pixel
  • An icon of the Unity section Unity
    • Unity
  • An icon of the Functional Programming section Functional Programming
    • Fundamentals of Functional Programming
    • .NET Functional Features
    • Signatures
    • Function Composition
    • Error Handling
    • Partial Application
    • Modularity
    • Category Theory
      • Overview
      • Monoid
      • Other Magmas
      • Functors
  • An icon of the Algorithms section Algorithms
    • Big O Notation
    • Array
    • Linked List
    • Queue
    • Hash Table and Set
    • Tree
    • Sorting
    • Searching
  • An icon of the Architecture section Architecture
    • What is architecture?
    • Domain-Driven Design
    • ASP.NET Core Projects

Ownership

At one point in time, only one variable may own a piece of data. If a given variable holds data on a stack, the data is just copied:

let a = 4;
let b = a;
// there will be two "4" on the stack

In the case of heap data, only the pointer is copied:

let a = String::from("abc");
let b = a;
// a is no longer a valid variable! b is the owner now!

Unique to Rust, the a variable becomes invalid when the code above gets executed. If we try to access it, it will result in a compilation error. Thanks to it, during runtime Rust will not try to drop/free this memory space twice, which would be wrong. It is called a move. a’s contents get moved to b.

Passing a value to a function also causes a move!

fn main() {
let s = String::from("abc");
some_func(s);
// s is no longer valid. The ownership was moved!
}
fn some_func(data: String) {
// At the end, `data` goes out of scope and `drop` is called
}

A function may also move ownership by returning a value. Then, the calling function’s scope owns the variable.

Ownership applies only to data on the heap!

References

Sometimes we want to pass a value to a function, but also to continue to use it in the calling function. We could make the called function return the passed-in value, but that would be a weird workaround, especially if that function is supposed to return some other data as well. References come to the rescue.

fn main() {
let s1 = String::from("hello");
// pass as a reference
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
// we can still use `s1`
}
fn calculate_length(s: &String) -> usize {
s.len()
// s has no ownership. When it goes out of scope, nothing happens
}

Creating a reference is called borrowing.

References are non-owning pointers because the ownership stays with the original owner. When a reference goes out of scope, Rust does not drop any heap memory.

References are immutable by default. We can change that:

fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}

Both the s variable and the some_string reference need to use the mut keyword to allow mutations of the value.

At any given time, you can have either:

  • one mutable reference
  • any number of immutable references.

This will fail:

let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s; // Wrong!
println!("{}, {}", r1, r2);

Also, if there is an immutable reference, another one that is mutable cannot be created:

let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
let r3 = &mut s; // BIG PROBLEM
println!("{}, {}, and {}", r1, r2, r3);

That’s because if one function gets an immutable reference, it will expect that the value should not change suddenly.

A reference’s scope starts from where it is introduced and continues through the last time that reference is used. This is OK:

let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s; // It's OK!
println!("{}", r2);

r1 gets created, but it is never used afterward. r2 may be created.

With Rust, it’s impossible to have dangling pointers. Rust will complain about such issues at compile time.

Dereferencing

let x = 5;
let y = &y;
assert_eq!(5, x);
assert_eq!(5, y); // WRONG! i32 and &i32 are different types

Sometimes we might need to obtain an actual value that the reference is pointing to. We can do that with the dereferencing operator:

let x = 5;
let y = &x;
assert_eq!(5, *y); // OK

Smart Pointers

Smart pointers might be dereferenced as well.

Slices

Slice is a part of some collection. It is a pointer to some index of that collection and a count of elements starting from that index.

let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];

String Slice

Sometimes we need to “extract” a part of a string. Instead of creating a totally new string, disconnected from the original one, we can use a String Slice.

let s = String::from("abcd");
let slice = &s[1..3];

slice contains the pointer to the first character of the slice, and the length of the slice. Additionally, the slice has a reference to s (?), so s cannot be mutated while slice is still in use.

All string literals are slices!

let s = "abc"; // It's a slice!

A reference to a string is treated as a string slice.

←  Basics
Structures  →
© 2023 Marcin Jahn | Dev Notebook | All Rights Reserved. | Built with Astro.