0% found this document useful (0 votes)
33 views19 pages

Lecture 14 Rust

This document discusses principles of concurrency and how Rust addresses the fundamental tension between safety and performance/control in concurrent systems. It explains that Rust uses a strong type system to prohibit unsafe behavior involving shared mutable state, allowing many concurrency errors to be caught at compile time. For data structures that cannot be checked, Rust allows them to be encapsulated as "unsafe" within otherwise safe APIs. It provides an example of how Rust's ownership and borrowing rules prevent data races by ensuring that resources have a single owner at any given time.

Uploaded by

saadiqah
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
33 views19 pages

Lecture 14 Rust

This document discusses principles of concurrency and how Rust addresses the fundamental tension between safety and performance/control in concurrent systems. It explains that Rust uses a strong type system to prohibit unsafe behavior involving shared mutable state, allowing many concurrency errors to be caught at compile time. For data structures that cannot be checked, Rust allows them to be encapsulated as "unsafe" within otherwise safe APIs. It provides an example of how Rust's ownership and borrowing rules prevent data races by ensuring that resources have a single owner at any given time.

Uploaded by

saadiqah
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 19

Principles of Concurrency

Lecture 14
Rust

1
Principles of Concurrency
Motivation
2

‣ Fundamental tension in concurrency between safety and


performance/control.
‣ Safety: statically identify bugs; data encapsulation
‣ Performance/control: manipulate low-level representation
without penalty

‣ Java, Haskell, OCaml, etc. give strong safety guarantees at


the expense of control
‣ C/C++ give control but at the expense of safety

Principles of Concurrency, Spring 2022


Rust
3

- Industry-supported language designed to overcome this tension


- Main idea: use a strong type-system to prohibit important kinds of unwanted
behavior, namely those involving mutation of shared state.
- Approach allows many kinds of systems programming errors (e.g, data
races, use-after-free) to be detected statically
- For data structures that cannot be checked in this way, Rust allows them
to encapsulated as “unsafe” within otherwise safe APIs

Principles of Concurrency, Spring 2022


emissions.
There was a really interesting study a few years ago that looked at the correlation between When
energy we ask Rust developers why they started using Rust, by far the most common answer is some variant of
consumption,
performance, and memory use. This is a really common conversation in sustainability.runtime performance,
Given how whether
little visibility it is because Rust is faster or because Rust has more reliable tail latencies. It’s almost
we have
into energy or carbon use by our services, is there a metric that can serve as a proxy? always about performance.

Sustainability
Can I look at my existing service
dashboards with infrastructure costs, performance, memory, etc and use the trends I see to infer something about the
trends in my service’s energy consumption?

What the study did is implement 10 benchmark problems in 27 different programming languages and measure execution
4 energy consumption, and peak memory use. C and Rust significantly outperformed other languages in energy
time,
efficiency. In fact, they were roughly 50% more efficient than Java and 98% more efficient than Python.

Low energy footprint


https://discord.com/blog/why-discord-is-switching-from-go-to-rust

Discord started as a mostly Python, Go, and Elixir shop, and they had a problem with one of their key Go services. It was a
pretty simple service, but it had slow tail latencies. Because Go is a garbage collection (GC) language, as objects are
created and released, every so often, the garbage collector needs to stop execution of the program and run a garbage
collection pass. While the GC is running, the process is unable to respond to requests, and you can see the spikes on the
CPU and response time graphs when it’s running.

Go Rust

It’s not a surprise that C and Rust are more efficient than other languages. What is shocking is the magnitude of the
difference. Broad adoption of C and Rust could reduce energy consumption of compute by 50% – even with a
conservative estimate.

with low latencies and


So the question is why not use more C? The language and developer tools are extremely mature, and the size of the
developer community is much bigger than Rust. During his keynote at Open Source Summit in 2021, Linus Torvalds, the

faster response times


creator of Linux, acknowledged that implementing code in C can be like juggling chainsaws. As a lifelong C programmer,
Torvalds knows that, “[C’s subtle type interactions] are not always logical [and] are pitfalls for pretty much anybody.”

Torvalds called Rust the first language he’s seen that might actually be a solution. Rust delivers the energy efficiency of C
without the risk of undefined behavior. We can cut energy use in half without losing the benefits of memory safety.

Several analyses have concluded that more than 70% of the high severity CVEs that occur in C/C++ would be prevented
by implementing those same solutions in Rust. In fact, the Internet Security Research Group (ISRG), the nonprofit that
supports the Let’s Encrypt project, the Certificate Authority for 260 million websites,To
hasfix the issue, Discord decided to try rewriting the service in Rust, and these are the results. The Go implementation is
Discord server
a goal to move all internet
on thesupport
security sensitive infrastructure to memory safe languages. The projects underway include left and
forthe Rust
Rust implementation
in the Linux is on the right. While the GC spike pattern is gone on the Rust graph, the really
Principles of Concurrency, Spring 2022
kernel and migrating curl to Rust implementations of TLS and HTTP. amazing difference is the magnitude of the change. The Go and Rust graphs are actually using different units.
The Problem Being Addressed
Instead of using a garbage collector to maintain safety, Rust uses ownership and borrow checking. Ownership is fairly
simple but has deep implications for the rest of the Rust programming language. In Rust, all memory is owned by a
single variable. That variable is called its owner. There can be only one owner at a time, but ownership of the data can be

5
passed around.

First, here is an example of message passing with Go. On the left side, we create a gift, then send it via the channel.
some other go routine on the right side, the gift is received and opened. The Go’s garbage collector is going to mana
the memory for us. However, in the code on the left side, we accidentally opened the gift after sending it into the
In Go
channel. The gift is going to be opened twice, resulting in a bug.

First, here is an example of message passing with Go. On the left side, we create a gift, then send it via the channel. On
some other go routine on the right side, the gift is received and opened. The Go’s garbage collector is going to manage
the memory for us. However, in the code on the left side, we accidentally opened the gift after sending it into the
channel. The gift is going to be opened twice, resulting in a bug.
contributed articles
In Rust Data Race
Figure 1. Use-after-free bug in C++ and how the bug is prevented in Rust. behind several layers
Here is the same message passing example with Rust. The gift is created and assigned. We say that the `gift`
particular when variab
code
owns the data. Ownership of the gift is passed into the channel. The channel consumer receives new features
the gift, get add
taking

}
1 std::vector<int> v { 10, 11 }; impossible to determ
ownership, and is able
2 intto *vptr
open it. If we try
= &v[1]; // to open*into*
Points the gift
v. after sending it into the channel, the compiler will shout
3 v.push_back(12);
C++ certain vector will i
because we are violating the ownership rules. Already, we
4 std::cout << *vptr; // Bug (use-after-free)
are seeing how Rust helps us prevent bugs.
elsewhere in the pro
v ing to be used again l
Here is the same message passing example with Rust. The gift is created and assigned. We say that the `gift` variable with
Comparison
owns the data. Ownership of the gift is passed into the channel. The channel consumer receives the gift, taking Languag
languages.
10 10
and will
ownership, and is able to open it. If we try to open the gift after sending it into the channel, the compiler OCaml
shoutavoid
at us, u
vptr 11 11
because we are violating the ownership rules. Already,12we are seeing how Rust helps us prevent bugs. using garbage colle
only deallocated whe
Use-after-free
}
1 let mut v = vec![10, 11]; be used by the prog
2 let vptr = &mut v[1]; // Points *into* 'v'. can be no dangling
Rust
3 v.push(12);
Because Rust enforces the rule that only
4 println!("{}", one//variable
*vptr); owns
Compiler data, when that variable goes out of use-after-free.
error scope without pass
Principles of Concurrency, Spring 2022 One problem wit
off ownership, there is no possible way for the data to be accessed. Rust takes advantage of that and will automatica
tion is that, to make
asing is
sing is to worry,
to worry, for
for instance,
instance, about
aboutclosing duceanother
closing duce anotherconcept:
concept:lifetimes.
lifetimes.Just
Just
Key Innovations: Ownership and Borrowing
ays has
ays
ated
has
by
ted by
files or
files orreleasing
releasinglocks.
locks.Using
Usingdestruc- likeininreal
destruc- like reallife,
life,when
when borrowing
borrowing some-
some-

6
Figure2.2.Rust
Figure Rustexample:
example:Moving
Movingownership.
ownership.
to
to our
our
oo con-
con- 11 fn
fn consume(w:
consume(w: Vec<i32>)
Vec<i32>){ { Could have been automatically
nn C++,
C++, 22 drop(w);
drop(w); //// deallocate
deallocatevector
vector inserted by the compiler
but 33 }}
but the
the
44 let
let vv == vec![10,
vec![10,11];
11];
copied
copied 55 consume(v);
consume(v);
uplicat-
uplicat- 66 v.push(12);
v.push(12); // // Compiler
Compilererror
error Ownership
ttto
tothe
the
pass-by-value
vv and
andww
m, but Figure
Figure3.3.Rust
Rustexample:
example:Mutable
Mutablereferences.
references.
am, but
ds to
ads to aa
ecause
because
o have
to have
11 fn
fn add_something(v:
add_something(v:&mut &mutVec<i32>)
Vec<i32>){ { track lifetimes
22 v.push(11);
he
he call,
call, 33 }}
v.push(11);
whatev-
whatev- 44 let
let mut
mut vv == vec![10];
vec![10];
er
er may
may 5
5
add_something(&mut v);
add_something(&mut v);
acking 6 v.push(12); // Ok!
backing 6 v.push(12); // Ok!
7 // v.push(12) is syntactic sugar for Vec::push(&mut v, 12)
7 // v.push(12) is syntactic sugar for Vec::push(&mut v, 12)
nership
nership Borrowing
memory
memory pass-by-reference
Principles of Concurrency, Spring 2022
Ownership
What about using a GC? (like Haskell, Java, Go, . . . ) What about using a GC? (like Haskell, Java, Go, . . . )
Garbage collection: Garbage collection:
The programmer allocates vectors, strings, etc. The programmer allocates vectors, strings, etc.
The runtime system periodically sweeps through memory, looks The runtime system periodically sweeps through memory, looks
7 for unreferenced data and deallocates it. for unreferenced data and deallocates it.
8 Loss of control 8 Loss of control
8 Runtime overhead
Ownership
8 Doesn’t help with other safety issues: iterator invalidation, data
8 Runtime overhead
Move S
8 Doesn’t help with other safety issues: iterator invalidation, data
races, etc. races, etc.
So, what is Rust’s solution? Ownership So, what is Rust’s solution? Ownership (affine linear typing)
A variable binding takes ownership of its data. [lifetimes]
A piece of data can only have one owner at a time. let v1 =
When a binding goes out of scope, the bound data is released let v2 =
println!
automatically.
For heap-allocated data, this means de-allocation. let v
Data must be guaranteed to outlive its references.
CIS 352 Rust Overview 13 / 1 CIS 352 Rust Overview 13 / 1
W
T
fn foo() { S
// Creates a Vec object.
Observation from C++-land Three Basic Patterns print
// Gives ownership of the Vec object to v1.
let mut v1 = vec![1, 2, 3]; W
v1.pop(); R
v1.push(4); M
// At the end of the scope, v1 goes out of scope. It
// v1 still owns the Vec object, so it Ownership fn foo(v: T) { ...}
can be cleaned up.
} Shared Borrow fn foo(v: &T) { ...}
Mutable Borrow fn foo(v: &mut T) { ...}
CIS 352 Rust Overview 16 / 1

The next bunch of slides are from


Ownership
) Outlaw doing both does not always have to be moved
at once and appoint the compiler Sheriff.
Borrow
CIS 198: Rust Programming, University of Pennsylvania, Spring 2016,
http://cis198-2016s.github.io/slides/01/

Principles of Concurrency, Spring 2022


CIS 352 Rust Overview 14 / 1 CIS 352 Rust Overview 15 / 1
println!("", v1[2]
automatically.

Move Semantics
What about using
Fora heap-allocated
GC? (like Haskell, Java,
data, this Go,de-allocation.
means ...) What about using a GC? (like Haskell,
let v2 Java, Go, .
= v1;
Data
Garbage collection: must be guaranteed to outlive its references. Garbage collection: We don’t wan
The programmer allocates vectors, strings, etc. The data cann
The programmer allocates vectors, strings, etc.
8
Move Semantics
The runtime system periodically
fn foo() { sweeps through memory, looks The runtime system periodically sweeps through memory,
Solution: molo
for unreferenced
// Creates a Vec it.object.
data and deallocates for unreferenced data and deallocates it.
8 Loss of control // Gives ownership of the Vec object to v1. 8 Loss of control println!("{}",
[lifetimes] 8 Runtime overhead let mut v1 = vec![1, 2, 3]; 8 Runtime overhead We know tha
8 Doesn’t help withv1.pop();
other safety issues: iterator invalidation, data 8 Doesn’t help with other safety issues: iteratorRust
invalidation,
can reasd
races, etc.
let v1 = vec![1, 2, 3]; races, etc.
v1.push(4); Moving own
eleased let v2
So, what is Rust’s solution? = v1;
Ownership // Ownership of the Vec object
So, moves
what is tosolution?
Rust’s v2. Ownership (affine linear typing) inv
// At the end of the scope, v1 goes out of scope. It doesn’t
println!("", v1[2]); // error: use of moved value ‘v1‘
// v1 still owns the Vec object, so it can be cleaned up.
}
let v2 = v1;
We don’t want to copy the data, since that’s expensive.
The data cannot haveCISmultiple
352 Rust Overview 16 / 1
owners.
Solution: move the Vec’s ownership into v2, and declare v1 invalid.
println!("{}", v1[2]);
CIS 352 Rust Overview 13 / 1 CIS 352 Rust Overview 13 / 1

Ownership does
We know thatnot
v1 isalways
no longerhave
a validto be moved
variable binding, ) error! Borrowing
Observation from C++-land ) compiler
Rust can reason about this at compile time,Three error.
Basic Patterns
Moving ownership is a compile-time semantic.
It doesn’t involve moving data during your program. In place of transf
aned up.
Rust would be a pain to write if we were forced to explicitly move A variable’s data
ownership back and forth. variable (i.e., alia

fn vector_length(v: Vec<i32>)CIS-> Vec<i32> { Ownership fn foo(v: When a reference


T) { ...}
352 Rust Overview 17 / 1
// Do whatever here, Shared Borrow fn foo(v: The
&T) original
{ ...} vari
// then return ownership of ‘v‘ back to the caller Mutable Borrow fn foo(v: &mut T) { ...}
} let v = vec![1, 2,
let v ref = &v; /
ed Borrowing
The more variables you had to hand back (think 5+), assert eq!(v[1], v
the longer your return type would be!
The next bunch of slides//are BUT!
from

) Outlaw doing both at once and appoint the compiler Sheriff.


CIS 198: Rust Programming, University oflet
Pennsylvania,
v_newSpring
= v; 2016, //
http://cis198-2016s.github.io/slides/01/
//
Principles of Concurrency, Spring 2022
In place of transferring ownership, we can borrow data.
CIS 352 Rust Overview 14 / 1 CIS 352 Rust Overview 15 / 1
Borrowing
CIS 352 Rust Overview 17 / 1
What about using a GC? (like Haskell, Java, Go, . . . ) What about using a GC? (like Haskell, Java, Go, . . . )
Garbage collection: Garbage collection:
The programmer allocates vectors, strings, etc. The programmer allocates vectors, strings, etc.
The runtime system periodically sweeps through memory, looks The runtime system periodically sweeps through memory, looks
d 9 Borrowing
for unreferenced data and deallocates it. for unreferenced data and deallocates it.
8 Loss of control 8 Loss of control
8 Runtime overhead 8 Runtime overhead
8 Doesn’t help with other safety issues: iterator invalidation, data 8 Doesn’t help with other safety issues: iterator invalidation, data
races, etc. races, etc.
In place of transferring ownership,So,we
So, what is Rust’s solution? Ownership whatcan borrow
is Rust’s data.
solution? Ownership (affine linear typing)

citly move A variable’s data can be borrowed by taking a reference to the


variable (i.e., aliasing); ownership doesn’t change.
When a reference goes out of scope, the borrow is over.
The original variable retains ownership throughout.
CIS 352 Rust Overview 13 / 1 CIS 352 Rust Overview 13 / 1

let v = vec![1, 2, 3];


let v ref = &v; // v ref is a reference to v.
Observation from C++-land
assert eq!(v[1], v ref[1]); // use Three
v refBasic
to Patterns
access the data
// in the vector v.
// BUT!
let v_new = v; // Error, cannot transfer ownership
// while references exist to it.
Ownership fn foo(v: T) { ...}
Shared Borrow fn foo(v: &T) { ...}
Mutable Borrow fn foo(v: &mut T) { ...}
CIS 352 Rust Overview 19 / 1

The next bunch of slides are from

) Outlaw doing both at once and appoint the compiler Sheriff.


CIS 198: Rust Programming, University of Pennsylvania, Spring 2016,
http://cis198-2016s.github.io/slides/01/

Principles of Concurrency, Spring 2022


CIS 352 Rust Overview 14 / 1 CIS 352 Rust Overview 15 / 1
Borrowing
What about using a GC? (like Haskell, Java, Go, . . . ) What about using a GC? (like Haskell, Java, Go, . . . )
Garbage collection: Garbage collection:
The programmer allocates vectors, strings, etc. The programmer allocates vectors, strings, etc.

10 Borrowing
The runtime system periodically sweeps through memory, looks
for unreferenced data and deallocates it. Borrowi
The runtime system periodically sweeps through memory, looks
for unreferenced data and deallocates it.
8 Loss of control 8 Loss of control
8 Runtime overhead 8 Runtime overhead
8 Doesn’t help with//other safety issues:
‘length‘ onlyiterator invalidation,
needs ‘vector‘data temporarily,
8 Doesn’tsohelpitwith
is other // ‘push‘
safety issues: iterator invalidation,
borrowed. data
races, etc. races, etc. fn push(ve
fn length(vec_ref: &Vec<i32>) -> usize {
So, what is Rust’s solution? Ownership So, what is Rust’s solution? Ownership (affine linear typing) vec ref
// vec_ref is auto-dereferenced when you call methods on it.
vec_ref.len() }
} fn main()
fn main() { let mu
let vector = vec![]; let ve
length(&vector); push(v
println!("{:?}", vector); // this is fine }
CIS 352 Rust Overview 13 / 1 CIS 352 Rust Overview 13 / 1

} Variab
References, like bindings, are immutable byBasic
default. ve
Observation from C++-land Three Patterns
Th
The borrow is over after the reference goes out of scope (at the
Differe
end of length).
You ca
(usize =The pointer-sized unsigned integer type.)
Also y
Ownership fn foo(v: T) { ...}
Shared Borrow fn foo(v: &T) { ...}
CIS 352 Rust Overview 20 / 1
Mutable Borrow fn foo(v: &mut T) { ...}

Borrowing Rules The next bunch of slides are from


Borrowi
) Outlaw doing both at once and appoint the compiler Sheriff.
CIS 198: Rust Programming, University of Pennsylvania, Spring 2016,
http://cis198-2016s.github.io/slides/01/

Principles of Concurrency, Spring 2022


CIS 352 Rust Overview 14 / 1 CIS 352 Rust Overview 15 / 1
Borrowing
// ‘length‘ only needs ‘vector‘ temporarily, so it is borrowed. // ‘push‘ needs to modify ‘vector‘ s
What about using a GC? (like Haskell, Java, Go, . . . ) What about using fn apush(vec
GC? (likeref: Haskell,
&mut Java, Go, . . .x:
Vec<i32>, ) i
fn length(vec_ref: &Vec<i32>) -> usize {
is auto-dereferenced when you call methods on it.Garbage collection: vec ref.push(x);
Garbage collection:
// vec_ref
The programmer allocates vectors, strings, etc.
vec_ref.len() } allocates vectors, strings, etc.
The programmer
}
11
Borrowing
The runtime system periodically sweeps through memory, looks
for unreferenced data and deallocates it.
fn main()
The runtime system
for unreferenced data and
{ sweeps through memory, looks
periodically
deallocates it.
fn main() { let mut vector: Vec<i32> = vec![
8 Loss of control 8 Loss of control let vector ref: &mut Vec<i32> =
let vector = vec![];
8 Runtime overhead 8 Runtime overhead
length(&vector); // ‘push‘ needs to modify ‘vector‘ so it is borrowed mutably. push(vector ref, 4);
s borrowed.
8 Doesn’t help with other safety issues: iterator invalidation, data 8 Doesn’t help with } other safety issues: iterator invalidation, data
println!("{:?}",
races, etc. fn push(vec
vector); // ref:
this &mut Vec<i32>, x: i32) { races, etc.
is fine
} onSo,it.
thods vec ref.push(x);
what is Rust’s solution? Ownership So, what is Rust’s solution?Variables
Ownership can (affinebe borrowed
linear typing) by mu
}
References, like bindings, are{immutable by default. vec ref is a reference to a mutab
fn main() The type is &mut Vec<i32>, not
The borrow is over after letthemutreference
vector:goes out of scope
Vec<i32> (at the
= vec![];
Different from a reference which i
end of length). let vector ref: &mut Vec<i32> = &mut vector;
push(vector ref, 4); You can have exactly one mutable
(usize =The pointer-sized unsigned integer type.)
} Also you cannot dereference borro
Variables can be borrowed by mutable reference: &mut vec ref.
CIS 352 Rust Overview 13 / 1 CIS 352 Rust Overview 13 / 1

Rustis
vec ref a reference
CIS 352
Overview 20 / 1 to a mutable Vec. CIS 352 Ru

Observation from C++-landThe type is &mut Vec<i32>, not &Vec<i32>.Three Basic Patterns
ope (at the
Different from a reference which is variable.
Borrowing Rules You can have exactly one mutable borrow at a time. Borrowing Prevents: Use-afte
Also you cannot dereference borrows (changes ownership).

Ownership fn foo(v: T) { ...}


CIS 352 Rust Overview 21 / 1
Shared Borrow fn foo(v: &T) { ...}
Summary Mutable Borrow
Valid fn C,
in foo(v: . . T) { ...}
C++,.&mut
let y: &i32;
1 Borrowing
You can’t keep borrowing Prevents:
something Use-after-free
after it stops existing. bugs {
2 One object may have many immutable references to it (&T). let x = 5;
The next
y bunch
= &x; of slides
// are from
error: ‘x‘ does not l
OR exactly one mutable reference (&mut T) (not both).
) Outlaw doing both at once and appoint the compiler Sheriff.
3 CIS 198: Rust Programming, University of Pennsylvania, Spring 2016,
}
http://cis198-2016s.github.io/slides/01/
println!("{}", *y);
Principles of Concurrency, Spring 2022
CIS 352 Rust Overview 14 / 1
This eliminates
CIS 352
vast numbers of memo
Rust Overview 15 / 1
CIS 352 Rust Overview 20 / 1 CIS 352 Rust Overview 21 / 1

What about using a GC? (like Haskell, Java, Go, . . . ) What about using a GC? (like Haskell, Java, Go, . . . )

ules Consequences
Garbage collection:
The programmer allocates vectors, strings, etc.
The runtime system periodically sweeps through memory, looks
Borrowing Prevents:
Garbage collection:
The Use-after-free bugsstrings, etc.
programmer allocates vectors,
The runtime system periodically sweeps through memory, looks
12 for unreferenced data and deallocates it. for unreferenced data and deallocates it.
8 Loss of control 8 Loss of control
8 Runtime overhead 8 Runtime overhead
8 Doesn’t help with other safety issues: iterator invalidation, data 8 Doesn’t help with other safety issues: iterator invalidation, data
races, etc. Valid in C, C++,. . . races, etc.
So, what is Rust’s solution? Ownership let y: &i32; So, what is Rust’s solution? Ownership (affine linear typing)
p borrowing something after it stops existing. {
ay have many immutable references to it (&T). let x = 5;
y = &x; // error: ‘x‘ does not live long enough
ne mutable reference (&mut T) (not both). }
println!("{}", *y);
The ownership rules also prevent other things Concurrency
This eliminates vast numbers of memory safety bugs at compile time!
Under the standard ownership CIS 352
rules:
Rust Overview 13 / 1 CIS 352 Rust Overview 13 / 1

You cannot implement doubly-linked lists (and circular structures


in general).
Observation
You cannot from call CC++-land
libraries. Three Basic Patterns
... The ownership rules also turn out to be u
CIS 352 Rust Overview 22 / 1 CIS 352 Rust Overview 23 / 1
safe concurrency (e.g., threads, interproc
Unsafe Rust
Standard currency primitives are not bui
unsafe { — they can be defined via ownership rul
...
Ownership Easy to fnuse, safe (e.g.,
foo(v: T) {data-race
...} free) con
}
big deal.
Shared Borrow fn foo(v: &T) { ...}
Relaxes some of the checking rules. Mutable Borrow fn foo(v: &mut T) { ...}
Allows C libraries calls.
Allows access to raw pointers.
Allows you to implement language extensions, e.g.,
The next bunch of slides are from
doubly-linked lists, garbage-collected pointers, etc.
) Outlaw doing both at once and appoint the compiler Sheriff.
CIS 198: Rust Programming, University of Pennsylvania, Spring 2016,
http://cis198-2016s.github.io/slides/01/
CIS 352 Rust Overview 24 / 1 CIS 352 Rust Overview

Principles of Concurrency, Spring 2022


CIS 352 Rust Overview 14 / 1 CIS 352 Rust Overview 15 / 1
Concurrency
13

use std::thread;
use std::time::Duration;

fn main() {
let handle = thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});

for i in 1..5 {
println!("hi number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}

handle.join().unwrap();
}

Synchronization point: main thread waits for child thread to complete


What happens if the join moves above the for loop?

Principles of Concurrency, Spring 2022


Concurrency
14

use std::thread;

fn main() {
let v = vec![1, 2, 3];
Why doesn’t this compile?
let handle = thread::spawn(|| {
println!("Here's a vector: {:?}", v);
});

handle.join().unwrap(); use std::thread;


}
fn main() {
let v = vec![1, 2, 3];

let handle = thread::spawn(|| {


println!("Here's a vector:
{:?}", v);
});
Reference to v in spawned
drop(v); // oh no!
thread would no longer
be valid handle.join().unwrap();
}
Principles of Concurrency, Spring 2022
Concurrency
15

Rust’s ownership rules require (among other things):


- there may exist at most one mutable reference to a value
- there may be any number of immutable references to a value

These two rules enforce a multiple-reader, single-writer discipline

fn main() {
int main() {
let mut msg = “Hello”.to_string();
std::string msg = “Hello”;
let handle = thread::spawn(|| {
std::thread t1([&]() {
println!(“{}, &msg);
std::cout << msg << std::endl;
});
});
msg.push_str(“,world”);
msg += “,world”;
handle.join().unwrap();
t1.join();
}
return 0;
}
Rust generates two errors:
- cannot borrow ‘msg’ as mutable; why?
- msg does not live long enough? why?
To x these errors - modify msg and then invoke the thread
Principles of Concurrency, Spring 2022
fi
Concurrency
16

use std::thread;
Move gives ownership to the spawned thread; the
fn main() { parent no longer has access to v
let v = vec![1, 2, 3];

let handle = thread::spawn(move || {


println!("Here's a vector: {:?}", v);
});

handle.join().unwrap();
}

Principles of Concurrency, Spring 2022


Shared Memory
17

use std::sync::Mutex; Unlock automatically is injected once


mut goes out of scope; the value
fn main() {
protected by the mutex (num)
let m = Mutex::new(5);
is a mutable reference
{
let mut num = m.lock().unwrap();
*num = 6;
} use std::sync::Mutex;
use std::thread;
println!("m = {:?}", m);
fn main() {
} let counter = Mutex::new(0);
let mut handles = vec![];

for _ in 0..10 {
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
Why does this not
compile? *num += 1;
});
handles.push(handle);
}

for handle in handles {


handle.join().unwrap();
}

println!("Result: {}", *counter.lock().unwrap());


}

Principles of Concurrency, Spring 2022


anony- lock. Moreover, notwhen
could not attemptstricted to acquire so the
as lock
to notat break memory
InLifetime
Rust, the or stricted
'a typical way toso as todata
share brea
ref
both threads: both clo
Sharing and Concurrency
at does the same time and thread
theresafety.
wouldConsider
be no inthe example
such a situation isscope,
in thread it automatica
safety.
to use an ConsiderAr
atomi- th
losures need for a lock inFigure the first6.place. &Mutex<Vec<i32>>,
cally reference-counted Figure
lock 6.
(an idiom
pointer: Arc<T> known thaa
he Rust The return type We
of again
lock, use structured
namely is a concurren-
pointer to T, but We
as
it alsoagain
RAII 31 use structure
). &i32
counts how tha add
18
on:” it Figure 5. Rust example: Shared references. vptr of type
MutexGuard<'a, cy T>, andis basically
shared the references, but cy exist
many such pointers and
In andour shared
deallo-
example, refe
en
of them same as &'a mutnow T: it we
grants
wrap exclusive
the vector cates
in the
our
T (and
a Mutex: now first
releases
both weits concurrency
wrap the
associated
threads vector
temporar ato
e done, access 1 to letthev T= that
vec![10,11];
is stored insidemthe resources) whenthreads
the
the can
lastvariable
pointer then
ismde-
ute us
x_
Rc
the variable ute x_v has type access to the vector,
. When 2 let vptr = &v[1];
stroyed. (This can Mutex<Vec<i32>>.
bemutable a form The
viewed asreference thr
, so we Mutex<Vec<i32>>.
3 join( || println!("v[1] = {}", The key operation
*vptr), concurrently.
of lightweight library-implemented
In fact, com
th
a The 4 actual type || ofprintln!("v[1]
on a Mutex
lock = {}",
wraps theisresult *vptr));
in a which
lock, blocks until onfact—but
a Mutex isthanks lock, whic to t
5 v.push(12);
LockResult<...> for error handling, which
lock
garbage collection.) it can take
Since a shared
the data
acquire theisexclusrat
sta
shared it can acquire the exclusive lock. The implementing mutua
explains why we use unwrap on lines 3 and 5. shared, we cannot ble reference—otherw
obtain
lock
lock implicitly gets released by v’s de- will never both have
an &mut
implicitly en
getsT releas
der the structor when the variab on
WhatFigure happens ifstructor
6. Rust example:v.push(12) when
Shared theis
mutable movedgoes
variable
state. intoout could
one ofence
of scope. not attempt
theUltimately,
threads?
at the same time to a
las
this pr
PRIL 2021 | VOL. 64 | NO. 4 scope. Ultimately, this program prints theeither
same
ness time and
property
[10,11,12] of muta th
if str
th
1 let mutex_v either [10,11,12] if 11]);
= Mutex::new(vec![10, the first thread need maintained.
for
manages a lock
to acquireIn
in other
the dea
the w
fi
2 join( manages to acquire the lock first, or [10, offer11] mutation
if the second of alias
thr
3 || { let mut v = mutex_v.lock().unwrap();
[10, 11] if the second thread does. The cause return
it type
implements neeo
4 v.push(12); }, In order to understand
5 || { let v In order to understand how our ex-
= mutex_v.lock().unwrap(); MutexGuard<'a,
ensuring
ample program that, during
type-check T>
do
6 println!("{:?}", *v) }); the
ample program type-checks, let us take same the
a closer asstate
&'a
look islock.
at not aliased
mut T:(alm
It
im
it
closer look at lock. It (almost ) has type fn(&'a
access Reference countin
a
toMutex<T>)
the T that -> M
is
exa
lock’s type is: fn(&'a Mutex<T>) -> MutexGuard <'a,that shared
T>. This references
type says th
the
Figure lock <'a,
canexample:
7. Rust be called T>. This type
with a shared
Reference says that lock can be called
share
counting.reference to mutex - allowing lock to withdata a shared
between referdi
dea
be invokedcalled with threads.
by multiple a sharedWhat reference
wouldto a mu-
happen
tex, which
a program. is why Rust letss
However,
a ifThelock took a type
actual of lock at
tex, both
which is why Rust lets us call lock on come with a staticallythreads: both closur
1 mutable
let arc_v1reference?
= Arc::new(vec![10, 11]); LockResult<...> forRue
2022 let arc_v2both
Principles of Concurrency, Spring2 &Mutex<Vec<i32>>,
threads: both closures capture an time, and when thattak
= Arc::clone(&arc_v1); andlif
explains why we use unw
Shared Memory
19

Atomic reference counting smart


use std::sync::{Arc, Mutex}; pointer
use std::thread;

fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];

for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();

*num += 1;
});
handles.push(handle);
}

for handle in handles {


handle.join().unwrap();
}

println!("Result: {}", *counter.lock().unwrap());


}

Principles of Concurrency, Spring 2022

You might also like