Python, Java, … are great languages
why are people using C, C++, etc.?
history + good support
‘‘zero overhead’’
no language VM — easier to distribute
idea: can avoid out-of-bounds, etc. with safety rules
… but safety rules don’t allow us to do some things fast
so: have ‘‘escape hatch’’ to avoid safety checks in those cases
hope: code that uses escape hatch can be tightly checked
com.sun.Unsafewhy do people not want to write
their performance-sensitive programs in Java?
hard to integrate code that uses escape hatch with normal Java code
hard to efficiently avoid dangling pointers when using escape hatch
slow to pass garbage collected references to/from C/assembly code
hard to avoid using garbage collector
default rules that only allow ‘safe’ things
escape hatch to use ‘‘raw’’ pointers or unchecked libraries
escape hatch can be used to write useful libraries
can write function that uses that impl:
or that works for anything with impl PartialEq:
use std::io;
fn main() {
println!("Enter a number: ");
let mut input = String::new();
// could have also written:
// let mut input: String = String::new();
io::stdin().read_line(&mut input);
// parse number or fail with an error message
let number: u32 = input.trim().parse()
.expect("That was not a number!");
println!("Twice that number is: {}", number * 2);
}use std::io;
fn main() {
println!("Enter a number: ");
let mut input = String::new();
// could have also written:
// let mut input: String = String::new();
io::stdin().read_line(&mut input);
// parse number or fail with an error message
let number: u32 = input.trim().parse()
.expect("That was not a number!");
println!("Twice that number is: {}", number * 2);
}mutable string variable input
&mut input takes mutable reference to it
(&input would be immutable reference)
use std::io;
fn main() {
println!("Enter a number: ");
let mut input = String::new();
// could have also written:
// let mut input: String = String::new();
io::stdin().read_line(&mut input);
// parse number or fail with an error message
let number: u32 = input.trim().parse()
.expect("That was not a number!");
println!("Twice that number is: {}", number * 2);
}u32 = 32-bit unsigned integer
parse() method figures out what to do based on return type
parse() returns Result object
result_object.expect("msg") = extract value, crash with "..." on errorstruct Rectangle {
width: u32, height u32
}
fn double_rectangle(rect: &mut Rectangle) {
rect.width *= 2;
rect.height *= 2;
}
fn show_rectangle(rect: &Rectangle) {
println!("{}x{}", rect.width, rect.height);
}
fn main() {
let mut rectangle = Rectangle { width: 4, height: 7 };
double_rectangle(&mut rectangle);
show_rectangle(&rectangle);
}fn mysum(vector: Vec<u32>) -> u32 {
let mut total: u32 = 0;
for value in &vector {
total += value
}
return total
}
fn foo() {
let vector: Vec<u32> = vec![1, 2, 3];
let sum = mysum(vector);
// **moves** vector into mysum()
// philosophy: no implicit expensive copies
println!("Sum is {}", sum);
// ERROR
println!("vector[0] is {}" , vector[0]);
}Compiling lecture-demo v0.1.0 (file:///home/cr4bd/spring2017/cs4630/...
error[E0382]: use of moved value: `vector`
--> src/main.rs:16:34
|
13 | let sum = mysum(vector);
| ------ value moved here
...
16 | println!("vector[0] is {}" , vector[0]);
| ^^^^^^ value used here after move
fn mysum(vector: Vec<u32>) -> u32 {
let mut total: u32 = 0
for value in &vector {
total += value
}
return total
}
fn foo() {
let vector: Vec<u32> = vec![1, 2, 3];
let sum = mysum(vector.clone());
// give away a copy of vector instead
// mysum will dispose, since it owns it
println!("Sum is {}", sum);
println!("vector[0] is {}" , vector[0]);
}fn mysum(vector: Vec<u32>) -> (u32, Vec<u32>) {
let mut total: u32 = 0
for value in &vector {
total += value
}
return (total, vector)
}
fn foo() {
let vector: Vec<u32> = vec![1, 2, 3];
let (sum, newVector) = mysum(vector);
// give away vector, get it back
println!("Sum is {}", sum);
println!("vector[0] is {}" , newVector[0]);
}mysum takes vector, then gives it back
internally doesn’t make copy to move vector
exactly one owner at a time
giving away ownership means you can’t use object
either give object new owner or deallocate
If called like p = foo(p), which follow single-owner rule?
will extend rule to be more flexible with two changes:
fn mysum(vector: &Vec<u32>) -> u32 {
let mut total: u32 = 0
for value in vector {
total += value
}
return total
}
fn foo() {
let vector: Vec<u32> = vec![1, 2, 3];
let sum = mysum(&vector);
// automates (vector, sum) = mysum(vector) idea
println!("Sum is {}", sum);
println!("vector[0] is {}" , vector[0]);
}error[E0106]: missing lifetime specifier
--> src/main.rs:19:25
|
19 | fn dangling_pointer() -> &mut i32 {
| ^ expected lifetime parameter
|
= help: this function's return type contains a borrowed value,
but there is no value for it to be borrowed from
error[E0515]: cannot return value referencing local variable `array`
--> src/lib.rs:4:12
|
4 | return &mut array[0]; // ERROR
| ^^^^^-----^^^
| | |
| | `array` is borrowed here
| returns a value referencing data owned by the current function
error[E0133]: use of mutable static is unsafe
and requires unsafe block
--> src/lib.rs:3:5
|
3 | ptr = &v[0];
| ^^^ use of mutable static
|
= note: mutable statics can be mutated by
multiple threads: aliasing violations
or data races will cause undefined behavior
error[E0597]: `v` does not live long enough
--> src/lib.rs:3:12
|
2 | fn dangling_pointer(v: Vec<i32>) -> i32 {
| - binding `v` declared here
3 | ptr = &v[0];
| -------^---
| | |
| | borrowed value does not live long enough
| assignment requires that `v` is borrowed for `'static`
4 | return v[0];
5 | }
| - `v` dropped here while still borrowed
fn add1(vector: &mut Vec<u32>) {
for value in vector {
*value += 1
}
}
fn foo() {
let mut vector: Vec<u32> = vec![1, 2, 3];
// what previous example was basically shorthand for
{
let borrowed = &mut vector;
// borrowing vector here...
add1(borrowed);
// until here
}
println!("vector[0] is {}" , vector[0]);
}compiler finds lifetime of borrowing
compiler checks for overlap with all other borrowings of that object
Exercise 1/2/3/4: The owner of x on line 1/2/3/4 is:
Rust rufuses to compile left-side: x being used while borrowed by p
Which changes would avoid this problem?
*p in the println!p mutable, reassign p = &mut x after line (4)fn get_min_max(v: &Vec<i32>) -> (&i32, &i32) {
let (mut min, mut max) = (&v[0], &v[0]);
for i in 1..v.len() {
if v[i] < *min { min = &v[i]; }
if v[i] > *max { max = &v[i]; }
}
return (min, max);
}
fn main() {
let vector = vec![1,2,3];
let (min, max) = get_min_max(&vector);
// min, max, println! below all borrowing vector
// okay because both immutable references
println!("vector = {:?}", vector);
println!("min = {}; max = {}", *min, *max);
}fn get_min_max(v: &Vec<i32>) -> (&i32, &i32) {
let (mut min, mut max) = (&v[0], &v[0]);
for i in 1..v.len() {
if v[i] < *min { min = &v[i]; }
if v[i] > *max { max = &v[i]; }
}
return (min, max);
}
fn main() {
let (min, max);
{
let vector = vec![1,2,3];
(min, max) = get_min_max(&vector);
// OKAY:
println!("min = {}; max = {}", *min, *max);
}
// ERROR:
println!("min = {}; max = {}", *min, *max);
}error[E0597]: `vector` does not live long enough
--> src/main.rs:14:34
|
13 | let vector = vec![1,2,3];
| ------ binding `vector` declared here
14 | (min, max) = get_min_max(&vector);
| ^^^^^^^ borrowed value does not live long enough
15 | }
| - `vector` dropped here while still borrowed
16 | println!("min = {}; max = {}", *min, *max);
| ---- borrow later used here
q referring to values that live too longobject is borrowed for duration of reference lifetime
lifetime of function args must include whole function call
references returned from function must have lifetimes
references stored in structs must have lifetime longer than struct
fn get_first_matching<'a, 'b>(prefix: &'a str, values: &'b Vec<String>)
-> &'b String {
for item in values {
if item.starts_with(prefix) {
return item
}
}
panic!()
}
fn get_first(values: &Vec<String>) -> &String {
let prefix: String = compute_prefix();
return get_first_matching(&prefix, values)
// prefix deallocated here
}error[E0499]: cannot borrow `*v` as mutable more than once at a time
--> src/main.rs:2:47
|
1 | fn get_mut_min_max(v: &mut Vec<i32>) -> (&mut i32, &mut i32) {
| - let's call the lifetime of this reference `'1`
2 | let (mut min, mut max) = (&mut v[0], &mut v[0]);
| - ^ second mutable borrow occurs here
| |
| first mutable borrow occurs here
...
7 | return (min, max);
| ---------- returning this value requires that `*v` is borrowed for `'1`
error[E0502]: cannot borrow `vector` as mutable because it is also borrowed as immutable
--> src/main.rs:11:10
|
9 | let first_elem: &u32 = &vector[0];
| ------ immutable borrow occurs here
10 | println!("*first_elem is {}", *first_elem);
11 | add1(&mut vector);
| ^^^^^^^^^^^ mutable borrow occurs here
12 | println!("*first_elem is {}", *first_elem);
| ----------- immutable borrow later used here
error[E0502]: cannot borrow `vector` as mutable because it is also borrowed as immutable
--> src/main.rs:11:10
|
9 | let first_elem: &u32 = &vector[0];
| ------ immutable borrow occurs here
10 | println!("*first_elem is {}", *first_elem);
11 | add1(&mut vector);
| ^^^^^^^^^^^ mutable borrow occurs here
12 | println!("*first_elem is {}", *first_elem);
| ----------- immutable borrow later used here
struct vec { int *data; int size; };
void append1(struct vec *v) {
v.data = realloc(v.data, sizeof(int) * (v.size + 1));
v.data[v.size] = 1;
v.size += 1;
}
void foo() {
struct vec vector;
vector.data = malloc(sizeof(int) * 3);
vector.data[0] = 1; vector.data[1] = 2; vector.data[2] = 3;
vector.size = 3;
int *first_elem = &vector.data[0];
printf("*first_elem is %d\n", *first_elem);
append1(&vector);
printf("*first_elem is %d\n"", *first_elem);
}fn add1(vector: &mut Vec<u32>) {
for value in vector {
*value += 1
}
}
fn foo() {
let mut vector: Vec<u32> = vec![1, 2, 3];
// (lifetime of first_elem starts here)
let first_elem: &mut u32 = &mut vector[0];
*first_elem += 1;
// (lifetime of first_elem ends here)
add1(&mut vector);
println!("vector is {:?}", vector); // [3, 3, 4]
}fn add1(vector: &mut Vec<u32>) {
for value in vector { *value += 1 }
}
fn foo() {
let mut vector: Vec<u32> = vec![1, 2, 3];
// (lifetime of first_elem starts here)
let first_elem: &mut u32 = &mut vector[0];
add1(&mut vector);
*first_elem += 1; // ERROR, two mutable borrowings of vector
// (lifetime of first_elem ends here)
println!("vector is {:?}", vector);
}error[E0499]: cannot borrow `vector` as mutable more than once at a time
--> src/main.rs:11:10
|
9 | let first_elem: &mut u32 = &mut vector[0];
| ------ first mutable borrow occurs here
10 | *first_elem += 1;
11 | add1(&mut vector);
| ^^^^^^^^^^^ second mutable borrow occurs here
12 | println!("first_elem is {}", *first_elem);
| ----------- first borrow later used here
error[E0499]: cannot borrow `vector` as mutable more than once at a time
--> src/main.rs:10:5
|
9 | let first_elem: &mut u32 = &mut vector[0];
| ------ first mutable borrow occurs here
10 | vector[1] += 2;
| ^^^^^^ second mutable borrow occurs here
11 | *first_elem += 1;
| ---------------- first borrow later used here
|
output:
A
in Example's drop
B
output:
A
B
in Example's drop
output:
A
in drop
B
in drop
drop functionssaw Rust’s Vec class — equivalent to C++ vector/Java ArrayList
idea: Vec wraps a heap allocation of an array
owner of Vec ‘‘owns’’ heap allocation
also Box class — wraps heap allocation of a single value
unique_ptrpub struct Vec<T> {
buf: RawVec<T>, // interface to malloc
len: usize,
}
impl<T> Vec<T> {
...
pub fn truncate(&mut self, len: usize) {
unsafe {
// drop any extra elements
while len < self.len {
// decrement len before the drop_in_place(), so a panic on Drop
// doesn't re-drop the just-failed value.
self.len -= 1;
let len = self.len;
ptr::drop_in_place(self.get_unchecked_mut(len));
}
}
}
...
}impl<T> Vec<T> {
...
pub const fn as_mut_ptr(&mut self) -> *mut T {
...
self.buf.ptr()
}
...
pub fn push(&mut self, value: T) {
// Inform codegen that the length does not change across grow_one().
let len = self.len;
// This will panic or abort if we would allocate > isize::MAX bytes
// or if the length increment would overflow for zero-sized types.
if len == self.buf.capacity() {
self.buf.grow_one();
}
unsafe {
let end = self.as_mut_ptr().add(len);
ptr::write(end, value);
self.len = len + 1;
}
}
...
}use libc;
fn main() {
let x: *mut u32 = unsafe { libc::malloc(16) as *mut u32 };
let pointer_value: usize = x as usize;
println!("{:?} {:#x}", x, pointer_value);
// 0x58a7196d5b10 0x58a7196d5b10
unsafe{*x = 0x123456;}
unsafe{*(x.add(1)) = 0x234567;}
let y: *mut u32 = (pointer_value+1) as *mut u32;
println!("{:#x}", unsafe{*y});
// release mode: 0x67001234
// debug mode: thread 'main' panicked at src/main.rs:10:30:
// misaligned pointer dereference: ....
}unsafe keyword to useunsafe internally
Rc<T> acts like &TRc<T> decrements shared countclone operation increments countuse std::rc::Rc;
fn main() {
let s_ref: &String;
let s1: Rc<String>;
{
let s2: Rc<String> = Rc::new(String::from("example"));
s1 = Rc::clone(&s2);
s_ref = &*s1;
println!("{s1} {s_ref} {s2}");
// example example example
println!("count={}", Rc::strong_count(&s1));
// count=2
}
println!("count={}", Rc::strong_count(&s1));
// count=1
println!("{s1} {s_ref}");
// example example
}use std::rc::Rc;
fn main() {
let s_ref: &String;
let s1: Rc<String>;
{
let s2: Rc<String> = Rc::new(String::from("example"));
s1 = Rc::clone(&s2);
s_ref = &*s2;
println!("{s1} {s_ref} {s2}");
println!("count={}", Rc::strong_count(&s0));
}
println!("count={}", Rc::strong_count(&s1));
println!("{s1} {s_ref}"); // ERROR
}
struct Grade {
score: i32, studentName: String, assignmentName: String,
}
struct Student {
name: String,
grades: Vec<Rc<Grade>>,
}
struct Assignment {
name: String,
grades: Vec<Rc<Grade>>
}
fn add_grade(student: &mut Student, assignment: &mut Assignment, score: i32) {
let grade = Rc::new(Grade {
score: score,
studentName: student.name.clone(),
assignmentName: assignment.name.clone(),
})
student.grades.push(Rc::clone(&grade));
assignment.grades.push(Rc::clone(&grade));
println!("Added grade with score={}", grade.score);
}Rc: only gives references to read-only objects
Rc: allows memory leaks via circular references
struct RcInner<T: ?Sized> {
strong: Cell<usize>, // <-- count of Rc<T>s pointing to this
weak: Cell<usize>, // <-- count of Weak<T>s pointing to this
value: T, // <-- actual data
}
pub struct Rc<T: ?Sized> {
ptr: NonNull<RcInner<T>>,
phantom: PhantomData<RcInner<T>>, // <- so compiler infers what operations are safe better
}probably what inspired Rust Box, Rc, etc.
std::shared_ptr (like Rc), std::unique_ptr (like Box)
operator* and operator-> to act like ‘normal’ pointerslike Rust, internally return temporary real references/pointers
problem: no compiler enforcement of ownership rules
can accidentally use ‘temporary’ reference/pointer for too long
raw pointers:
C++
vector<shared_ptr<int>> values;
values.push_back(make_shared<int>(10));
values.push_back(make_shared<int>(20));
shared_ptr<int> p{values[0]};
shared_ptr<int> q{values[0]};
*p += 1;
cout << *p << " " << *q << " " << *values[0] << endl; // 11 11 11
cout << values[0].use_count() << endl; // 3
cout << values[1].use_count() << endl; // 1
values.clear();
cout << p.use_count() << endl; // 2
p.reset(new int(30));
cout << q.use_count() << endl; // 1Rust
let mut values: Vec<Arc<Cell<i32>>> = vec![];
values.push(Arc::new(Cell::new(10)));
values.push(Arc::new(Cell::new(20)));
let mut p: Arc<Cell<i32>> = Arc::clone(&values[0]);
let q: Arc<Cell<i32>> = Arc::clone(&values[0]);
(*p).replace((*p).get() + 1);
println!("{} {} {}", (*p).get(), (*q).get(), (*values[0]).get()); // 11 11 11
println!("{}", Arc::strong_count(&values[0])); // 3
println!("{}", Arc::strong_count(&values[1])); // 1
values.clear();
println!("{}", Arc::strong_count(&p)); // 2
p = Arc::new(Cell::new(30));
println!("{}", Arc::strong_count(&q)); // 1escape hatch: make new reference-like types
RefCell: borrow_mut() method gives mutable-ref-like object
RefCell<T> x: x.borrow() gives immutable-ref-like object
callbacks on ownership ending (normally deallocation)
choice of what happens on move/copy
runtime-enforced version of Rust borrowing rules
borrow_mut(): give &mut T-like object only if other active borrows
&mut T no longer in useborrow(): give &T-like object only if no active mutable borrows
&T no longer in usefn myadd(x: &RefCell<i32>, y: &RefCell<i32>, z: &RefCell<i32>) {
let mut x_value = x.borrow_mut();
let y_value = y.borrow();
let z_value = z.borrow();
*x_value += *y_value;
*x_value += *z_value;
println!("{}, {}, {}", x_value, y_value, z_value);
}
fn main() {
let x: RefCell<i32> = RefCell::new(1);
let y: RefCell<i32> = RefCell::new(2);
let z: RefCell<i32> = RefCell::new(3);
myadd(&x, &y, &z); // 6, 2, 3
myadd(&x, &y, &y); // 10, 2, 2
myadd(&x, &x, &x); // RUNTIME ERROR
}fn appendsum(x: &RefCell<Vec<i32>>, y: &RefCell<Vec<i32>>, z: &RefCell<Vec<i32>>) {
let mut x_value = x.borrow_mut();
let y_value = y.borrow();
let z_value = z.borrow();
let i = 0;
for (y_number, z_number) in y_value.iter().zip(z_value.iter()) {
x_value.push(y_number + z_number);
}
println!("{:?}", *x_value)
}
fn main() {
let x: RefCell<Vec<i32>> = RefCell::new(vec![1]);
let y: RefCell<Vec<i32>> = RefCell::new(vec![2]);
let z: RefCell<Vec<i32>> = RefCell::new(vec![3]);
appendsum(&x, &y, &z);
appendsum(&x, &y, &y);
appendsum(&x, &x, &x);
}pub struct RefCell<T: ?Sized> {
// mutable integer
// set to -1 on mutable borrow
// incremented on immutable borrow
borrow: Cell<BorrowFlag>,
value: UnsafeCell<T>,
}
pub fn borrow_mut(&self) -> RefMut<'_, T> { ... }
pub struct RefMut<'b, T: ?Sized + 'b> {
value: NonNull<T>,
borrow: BorrowRefMut<'b>,
...
}Deref, DerefMut)RefMut acts like a mutable reference value
compiler automatically drops when it goes out of scope
compiler knows RefMut contains item with lifetime ’b
struct BorrowRefMut<'b> {
borrow: &'b Cell<BorrowFlag>,
}
impl Drop for BorrowRefMut<'_> {
fn drop(&mut self) {
let borrow = self.borrow.get(); self.borrow.set(borrow + 1);
}
}
impl<'b> BorrowRefMut<'b> {
fn new(borrow: &'b Cell<BorrowFlag>) -> Option<BorrowRefMut<'b>> {
match borrow.get() {
UNUSED => {
borrow.set(UNUSED - 1); Some(BorrowRefMut { borrow })
}
_ => None,
}
}
}given x: Rc<Foo> variable calling x.clone() on two cores
x.clone on core A x.clone on core B
-------------------------------------------
x.inc_strong():
temp <- self.count
x.inc_strong():
temp <- self.count
self.count <- temp + 1
self.count <- temp + 1
one option: require Rc implementation to handle mutiple cores
Rust solution: different types for multithreaded/multicore code
two ‘‘traits’’ to mark custom types:
two implementations of referenc counting
saw: enforcing no use-after-free
lots of coding conventions we might try to enforce:
code’s runtime does not depend on secret data
sensitive data not passed to wrong place
code has bounded runtime
Box<...> to represent object on the heapOption<Box<...>> to represent pointer.saw: enforcing no use-after-free
lots of coding conventions we might try to enforce:
code’s runtime does not depend on secret data
sensitive data not passed to wrong place
code has bounded runtime
active research area, no consensus on what works best
common approach: separate type for secret data
compiler or language virtual machine disallows variable-time operations using secret data
no secret-based array lookup (cache timing varies)
no secret-based integer division (usually variable speed instruction)
…
explicit operations for any secret-to-non-secret conversions
Box<...> to represent object on the heapOption<Box<...>> to represent pointer.