Experimental Rust Book - https://rust-book.cs.brown.edu 2023-11-16 Unterschied zum normalen "The Book" vor allem in folgenden Kapiteln: Kap. 4 Kap. 6 Kap. 8 Kap. 10 Kap. 17 Siehe das "Ownership Inventory", es gibt vier Teile in den Kapiteln 6, 8, 10 und 17. Die Quizfragen werden anscheinend durcheinandergewuerfelt bei wiederholtem Aufruf. Die Quizzes werden in .toml Files beschrieben. Es gibt eine mdbook Erweiterung (Preprocessor), der daraus Quizzes macht: https://github.com/cognitive-engineering-lab/mdbook-quiz In Kap. 6 wird eine "In-Browser IDE" verwendet (Rust-Analyzer in Webasm kompiliert), so dass man die Maus ueber Quelltext bewegen kann und dann manche Sachen angezeigt bekommt. Geht aber bei mir nur mit Firefox, nicht mit Vivaldi. In den Fragen von Kap. 4 fehlen noch einige Bilder. Am besten hier in das originale Buch schauen, siehe Platzhalter . XXX Todo: Kap. 14, 16, 17, 18 Chapter 1 --------- Getting Started * What is the name of the command-line tool for managing the version of Rust on your machine? * Every executable Rust program must contain a function with the name: * Let's say you have the following program in a file hello.rs: fn main() { println!("Hello world!"); } Say you then run the command rustc hello.rs from the command-line. Which statement best describes what happens next?  - rustc will print an error because this is an invalid program - rustc reformats hello.rs according to the Rust style guide  - rustc executes the program and prints out Hello world! - rustc generates a binary executable named hello * Say you just downloaded a Cargo project, and then you run cargo run at the command-line. Which statement is NOT true about what happens next? - Cargo executes the project's binary - Cargo downloads and builds any dependencies of the project - Cargo builds the project into a binary in the target/debug directory - Cargo watches for file changes and re-executes the binary on a change Chapter 2 --------- A Guessing Game Kein Quiz Chapter 3 --------- Common Programming Concepts Which statement best describes what it means if a variable x is immutable? - x cannot be changed after being assigned a value. - You cannot create a reference to x. - After being defined, x can be changed at most once. - x is stored in the immutable region of memory. What is the keyword used after let to indicate that a variable can be mutated? Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn main() { let x = 1; println!("{x}"); x += 1; println!("{x}"); }  This program:  DOES compile OR  does NOT compile --- Which of the following statements is correct about the difference between using let and const to declare a variable? - const can be used in the global scope, and let can only be used in a function  - They are just different syntaxes for declaring variables with the same semantics - A const can only be assigned to a literal, not an expression involving computation  - The compiler will error if a const variable's name is not using UPPER_SNAKE_CASE --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. const TWO: u32 = 1 + 1; fn main() { println!("{TWO}"); }  This program:  DOES compile OR does NOT compile --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn main() { let mut x: u32 = 1; { let mut x = x; x += 2; } println!("{x}"); }  This program: DOES compile OR does NOT compile --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn main() { let mut x: u32 = 1; x = "Hello world"; println!("{x}"); }  This program:  DOES compileOR does NOT compile ---- The largest number representable by the type i128 is:  - 2^128 - 2^128 - 1 - 2^127 - 2^127 - 1 --- if x : u8 = 0, what will happen when computing x - 1? - It will always return 255. - It will always panic. - It depends on the compiler mode. --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn main() { let x: fsize = 2.0; println!("{x}"); }  This program:  DOES compile OR does NOT compile ---- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn main() { let message = "The temperature today is:"; let x = [message, 100]; println!("{} {}", x[0], x[1]); }  This program:  DOES compileOR does NOT compile ---- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn main() { let t = ([1; 2], [3; 4]); let (a, b) = t; println!("{}", a[0] + t.1[0]); }  This program:  DOES compile OR does NOT compile ---- The keyword for declaring a new function in Rust is: Your input: ________ --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn f(x) { println!("{x}"); } fn main() { f(0); }  This program:  DOES compile OR does NOT compile --- In Rust, a curly-brace block like { /* ... */ } is: 1 An expression 2 A statement 3 A syntactic scope   - 2 and 3 - 1 and 3 - 1 only - 2 only --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn f(x: i32) -> i32 { x + 1 } fn main() { println!("{}", f({ let y = 1; y + 1 })); }  This program:  DOES compile OR does NOT compile --- True/false: executing these two pieces of code results in the same value for x. Snippet 1: let x = if cond { 1 } else { 2 }; Snippet 2: let x; if cond { x = 1; } else { x = 2; } (Note: both of these snippets compile!) False True --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn main() { let x = 1; let y = if x { 0 } else { 1 }; println!("{y}"); }  This program:  DOES compile OR does NOT compile --- True/false: this code will terminate (that is, it will not loop forever). fn main() { let mut x = 0; 'a: loop { x += 1; 'b: loop { if x > 10 { continue 'a; } else { break 'b; } } break; } }   True False ---- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn main() { let a = [5; 10]; let mut sum = 0; for x in a { sum += x; } println!("{sum}"); }  This program:  DOES compile OR does NOT compile Chapter 4 --------- Understanding Ownership __4.1__ Which of the following is NOT a kind of undefined behavior? - Using a pointer that points to freed memory - Freeing the same memory a second time - Having a pointer to freed memory in a stack frame -Using a non-boolean value as an if condition --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn add_suffix(mut s: String) -> String { s.push_str(" world"); s } fn main() { let s = String::from("hello"); let s2 = add_suffix(s); println!("{}", s2); }  This program:  DOES compile OR does NOT compile --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn main() { let s = String::from("hello"); let s2; let b = false; if b { s2 = s; } println!("{}", s); }  This program:  DOES compile OR does NOT compile --- Say we have a function that moves a box, like this: fn move_a_box(b: Box) { // This space intentionally left blank } Below are four snippets which are rejected by the Rust compiler. Imagine that Rust instead allowed these snippets to compile and run. Select each snippet that would cause undefined behavior, or select "None of the above" if none of these snippets would cause undefined behavior. - None of the above - let b = Box::new(0); move_a_box(b); println!("{}", b);  - let b = Box::new(0); let b2 = b; println!("{}", b); move_a_box(b2);  - let b = Box::new(0); let b2 = b; move_a_box(b); - let b = Box::new(0); move_a_box(b); let b2 = b; __4.2__ References and Borrowing Consider the following program, showing the state of memory after the last line: let x = Box::new(0); let y = Box::new(&x);L1 If you wanted to copy out the number 0 through y, how many dereferences would you need to use? Write your answer as a digit. For example, if the correct expression is *y, then the answer is 1. --- Consider the following program, showing the state of memory after the last line: fn get_first(vr: &Vec) -> i32 { vr[0] } fn main() { let mut v = vec![0, 1, 2]; let n = get_first(&v); println!("{} {}", n, v[1]);L1 } Which of the following best explains why v is not deallocated after calling get_first? - vr is not mutated within get_first - v is used after calling get_first in the println - get_first returns a value of type i32, not the vector itself - vr is a reference which does not own the vector it points to --- Consider the permissions in the following program: let mut s = String::from("Hello"); let t = &mut s; /* here */ t.push_str(" world"); println!("{}", s); At the point marked /* here */, what are the permissions on the path s? Select each permission below, or select "no permissions" if the path has no permissions. - R - W - O - No permissions --- Consider the permissions in the following program: Which of the following best explains why strs loses and regains write permissions? - get_first returns an immutable reference to data within strs, so strs is not writable while first is live  - strs is not writable while the immutable reference &strs passed to get_first is live  - strs does not need write permissions until the strs.push(..) operation, so it only regains write permissions at that statement  - Because first refers to strs, then strs can only be mutated within a nested scope like the if-statement --- Consider this unsafe program: let v1 = vec![1, 2, 3]; let mut v2 = v1; v2.push(4); println!("{}", v1[0]);L1 Which of the following best describes the point at which undefined behavior occurs in this program? Response  - v1 has been moved into v2 on line 2  - v1[0] reads v1, which points to deallocated memory - v1 has its pointer invalidated by the push on line 3  - v2 owns the vector data on the heap, while v1 does not --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn incr(n: &mut i32) { *n += 1; } fn main() { let mut n = 1; incr(&n); println!("{n}"); }  This program:  DOES compile OR does NOT compile --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn main() { let mut s = String::from("hello"); let s2 = &s; let s3 = &mut s; s3.push_str(" world"); println!("{s2}"); }  This program:  DOES compile OR does NOT compile --- Consider this Rust function that pushes a number onto the end of a vector, and then removes and returns the number from the front of the vector: fn give_and_take(v: &Vec, n: i32) -> i32 { v.push(n); v.remove(0) } Normally, if you try to compile this function, the compiler returns the following error: error[E0596]: cannot borrow `*v` as mutable, as it is behind a `&` reference --> test.rs:2:5 | 1 | fn give_and_take(v: &Vec, n: i32) -> i32 { | --------- help: consider changing this to be a mutable reference: `&mut Vec` 2 | v.push(n); | ^^^^^^^^^ `v` is a `&` reference, so the data it refers to cannot be borrowed as mutable Assume that the compiler did NOT reject this function. Select each (if any) of the following programs that could possibly cause undefined behavior if executed. If none of these programs could cause undefined behavior, then check "None of these programs" .  -  let v = vec![1, 2, 3]; let n = &v[0]; give_and_take(&v, 4); println!("{}", n);  - None of these programs - let v = vec![1, 2, 3]; let v2 = &v; give_and_take(&v, 4); println!("{}", v2[0]);  - let v = vec![1, 2, 3]; let n = &v[0]; let k = give_and_take(&v, 4); println!("{}", k); --- __4.3__ Fixing Ownership Errors Which of the following is NOT a valid kind of fix to the issue of returning a stack reference from a function? - Take ownership of the returned value - Use a reference-counted pointer - Extend the lifetime of the stack frame - Expect a mutable slot from the caller --- Let's say a programmer tried writing the following function: /// Returns a person's name with "Ph.D." added as a title fn award_phd(name: &String) -> String { let mut name = *name; name.push_str(", Ph.D."); name } The Rust compiler rejects their code with the following error: error[E0507]: cannot move out of `*name` which is behind a shared reference --> test.rs:3:20 | 3 | let mut name = *name; | ^^^^^ | | | move occurs because `*name` has type `String`, which does not implement the `Copy` trait | help: consider borrowing here: `&*name` Given the stated purpose of the function, which of the following would be the most idiomatic fix to the program? The differences from the function above are highlighted. Response  - fn award_phd(name: &String) -> String { let mut name = &*name; name.push_str(", Ph.D."); name }  - fn award_phd(name: &String) -> String { let mut name = name.clone(); name.push_str(", Ph.D."); name }  - fn award_phd(name: &mut String) { name.push_str(", Ph.D."); }  - fn award_phd(mut name: String) -> String { name.push_str(", Ph.D."); name } --- Let's say a programmer tried writing the following function: /// Rounds all the floats in a vector to the nearest integer, in-place fn round_in_place(v: &Vec) { for n in v { *n = n.round(); } } The Rust compiler rejects their code with the following error: error[E0594]: cannot assign to `*n`, which is behind a `&` reference --> test.rs:4:9 | 3 | for n in v { | - this iterator yields `&` references 4 | *n = n.round(); | ^^^^^^^^^^^^^^ `n` is a `&` reference, so the data it refers to cannot be written Given the stated purpose of the function, which of the following would be the most idiomatic fix to the program? The differences from the function above are highlighted. Response  - fn round_in_place(v: &Vec) -> Vec { let mut v2 = Vec::new(); for n in v { v2.push(n.round()); } v2 }  - fn round_in_place(mut v: Vec) { for n in v { *n = n.round(); } }  - fn round_in_place(v: &Vec) { for mut n in &mut v.clone() { n = n.round(); } }  - fn round_in_place(v: &mut Vec) { for n in v { *n = n.round(); } } --- Which of the following best explains why an i32 can be copied without a move, but a String cannot?  - A String can be placed on the heap, while an i32 can only be placed on the stack - An i32 is smaller in memory than a String - An i32 is a primitive type in Rust, while a String is not - A String owns data on the heap, while an i32 does not --- The following code snippet does not compile: let s = String::from("Hello world"); let s_ref = &s; let s2 = *s_ref; println!("{s2}"); Which of the following best describes the undefined behavior that could occur if this program were allowed to execute? Response - The dereference *s_ref is a use of freed memory  - The string is freed twice at the end of the program  - The read of s2 in println is a use of freed memory  - There is no undefined behavior in this program --- The following program does not compile: fn copy_to_prev(v: &mut Vec, i: usize) { let n = &mut v[i]; *n = v[i - 1]; } fn main() { let mut v = vec![1, 2, 3]; copy_to_prev(&mut v, 1); } Which of the following best describes the undefined behavior that could occur if this program were allowed to execute? Response   - The read of v[i - 1] is a use of freed memory  - The assignment *n is a use of freed memory  - There is no undefined behavior in this program  - The borrow &mut v[i] creates a pointer to freed memory --- Consider this function that is a simplified variant of the function from the previous quiz: /// Adds "Ph.D." to a person's name fn award_phd(name: &String) { let mut name = *name; name.push_str(", Ph.D."); } The Rust compiler rejects this function with the following error: error[E0507]: cannot move out of `*name` which is behind a shared reference --> test.rs:3:20 | 3 | let mut name = *name; | ^^^^^ | | | move occurs because `*name` has type `String`, which does not implement the `Copy` trait | help: consider borrowing here: `&*name` Assume that the compiler did NOT reject this function. Select each (if any) of the following programs that could possibly cause undefined behavior if executed. If none of these programs could cause undefined behavior, then check "None of these programs" . Response  - let name = String::from("Ferris"); let name_ref = &name; award_phd(&name); println!("{}", name_ref);  - None of these programs - let name = String::from("Ferris"); award_phd(&name);  - let name = String::from("Ferris"); award_phd(&name); println!("{}", name); --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn main() { let mut point = [0, 1]; let mut x = point[0]; let y = &mut point[1]; x += 1; *y += 1; println!("{} {}", point[0], point[1]); }  This program:  DOES compile OR does NOT compile __4.4__ The Slice Type Consider the variables s2 and s3 in the following program. These two variables will be located in memory within the stack frame for main. Each variable has a size in memory on the stack, not including the size of pointed data. Which statement is true about the sizes of s2 and s3? fn main() { let s = String::from("hello"); let s2: &String = &s; let s3: &str = &s[..]; }   - s3 has more bytes than s2  - s3 has the same number of bytes as s2  - s3 has fewer bytes than s2 --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn main() { let mut s = String::from("hello"); for &item in s.as_bytes().iter() { if item == b'l' { s.push_str(" world"); } } println!("{s}"); }  This program:  DOES compile OR does NOT compile __4.5__ Ownership Recap Say you are writing a function with the following spec: round_all takes as input a list of floating point numbers, and it rounds each number in-place to the nearest integer. Which of the following is the most appropriate type signature for a function implementing this spec?  - fn round_all(v: &Vec);  - fn round_all(v: &Vec) -> Vec;  - fn round_all(v: &mut Vec);  - fn round_all(v: Vec); --- Say you are writing a function with the following spec: find_contains takes as input a collection of strings and a target substring. It returns a list of all the strings in the collection that contain the target substring. Which of the following is the most appropriate type signature for a function implementing this spec?  - fn find_contains(haystack: &[String], needle: &str) -> Vec<&String>;  - fn find_contains(haystack: &Vec, needle: &str) -> &[String];  - fn find_contains(haystack: &Vec, needle: String) -> Vec;  - fn find_contains(haystack: &[String], needle: &str) -> Vec; --- Rust normally disallows multiple mutable accesses to the same array, even when those accesses are disjoint. For example, this function does not compile: fn main() { let mut v = vec![0, 1, 2, 3]; let (r0, r1) = (&mut v[0..2], &mut v[2..4]); r0[0] += 1; r1[0] += 1; } However, the Rust standard library has a function slice::split_at_mut that does permit this functionality: fn main() { let mut v = vec![0, 1, 2, 3]; let (r0, r1) = v.split_at_mut(2); r0[0] += 1; r1[0] += 1; } Which of the following best describes how it's possible to implement split_at_mut? - split_at_mut is a special compiler primitive that cannot be implemented within the language  - split_at_mut uses unsafe code to disable the borrow checker from checking the safety of mutable references  - split_at_mut uses unsafe code to circumvent the borrow checker with raw pointers  - split_at_mut calls into a C library that can't be analyzed by Rust --- Consider the permissions in the following program: ❓ let s = String::new();» let s_ref = &  Which of the following best explains why *s_ref does not have the O (own) permission?  - Ownership permits reading, and reading *s_ref can cause a use-after-free  - Ownership permits moving, and moving out of a reference can cause a double-free - Ownership permits borrowing, and reborrowing *s_ref can cause a double-free  - Ownership permits mutation, and mutating *s_ref can cause a use-after-free --- Consider the set of Rust programs that contain no unsafe code. Select each of the following statements that is true about the kinds of programs accepted and rejected by the borrow checker:  - The borrow checker always accepts programs without undefined behavior  - The borrow checker sometimes accepts programs with undefined behavior - The borrow checker sometimes rejects programs without undefined behavior - The borrow checker always rejects programs with undefined behavior --- - The function extract is rejected by the borrow checker: fn extract(b: &Box) -> i32 { let b2: Box = *b; *b2 } Imagine that the borrow checker did not reject this function. Determine whether there exists an input such that the function would cause undefined behavior if executed on that input.  - This function could NOT cause undefined behavior  - This function COULD cause undefined behavior --- The function transfer_string is rejected by the borrow checker: fn get_first(strs: &mut (String, String)) -> &mut String { &mut strs.0 } fn get_second(strs: &mut (String, String)) -> &mut String { &mut strs.1 } fn transfer_string(strs: &mut (String, String)) { let fst = get_first(strs); let snd = get_second(strs); fst.push_str(snd); snd.clear(); } Imagine that the borrow checker did not reject this function. Determine whether there exists an input such that the function would cause undefined behavior if executed on that input. Response  - This function could NOT cause undefined behavior - This function COULD cause undefined behavior Chapter 5 --------- Using Structs __5.1__ Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. struct Point { x: i32, y: i32, } fn main() { let mut a = Point { x: 1, y: 2 }; a.x += 1; let b = Point { y: 1, ..a }; a.x += 1; println!("{}", b.x); }  This program:  DOES compile OR does NOT compile ---- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. struct Point { x: i32, y: i32, } fn main() { let mut p = Point { x: 1, y: 2 }; let x = &mut p.x; let y = &mut p.y; *x += 1; *y += 1; println!("{} {}", p.x, p.y); }  This program:  DOES compileOR does NOT compile __5.2__ Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. #[derive(Debug)] struct Rectangle { width: u32, height: u32, } fn main() { let rect1 = Rectangle { width: 30, height: 50, }; let a = area(rect1); println!("{} * {} = {}", rect1.width, rect1.height, a); } fn area(rectangle: Rectangle) -> u32 { rectangle.width * rectangle.height }  This program:  DOES compile OR does NOT compile --- Which statement best describes a difference between the Display and Debug traits? - There is no difference, Display and Debug are aliases for the same trait.  - Display is for presenting values to an end-user, while Debug is for developers' internal use - Display is for printing values to the console, while Debug is for viewing values in a debugger  - Display cannot be implemented for structs, while Debug can be implemented for structs __5.3__ What is the keyword for constructor functions in Rust? - constructor - new - The name of the type being constructed - None of the above --- - Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. struct Point(i32, i32); fn main() { let p = Point(1, 2); impl p { fn x(&self) -> i32 { self.0 } } println!("{}", p.x()); }  This program:  DOES compile OR does NOT compile --- Say you have a variable v of type &mut Vec, and you want to call the len method with the following signature: impl Vec { fn len(&self) -> usize { /* ... */ } } If you try to compile the expression v.len(), which of the following statements best describes what happens? - It compiles, because &self can take any kind of reference  - It compiles, because the &mut reference is implicitly reborrowed as an & reference  - It does not compile, v is not explicitly dereferenced - It does not compile, because &mut Vec is not the same type as &Vec --- Consider these two methods that increment a field of a struct. Which style would be more idiomatic for Rust? struct Point(i32, i32); impl Point { fn incr_v1(mut self) { self.0 += 1; } fn incr_v2(&mut self) { self.0 += 1; } }   - incr_v1 - incr_v2 - Both are idiomatic - Neither are idiomatic --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. struct Point(i32, i32); impl Point { fn incr_x(&mut self) { self.0 += 1; } } fn main() { let mut p = Point(0, 0); p.incr_x(); println!("{}", p.0); }  This program:  DOES compile OR does NOT compile --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. struct Point { x: i32, y: i32 } impl Point { fn get_x(&mut self) -> &mut i32 { &mut self.x } } fn main() { let mut p = Point { x: 1, y: 2 }; let x = p.get_x(); *x += 1; println!("{} {}", *x, p.y); }  This program:  DOES compile OR does NOT compile --- Chapter 6 --------- Enums and Pattern Matching __6.1__ Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn foo(x: &i32) { println!("{x}"); } fn main() { let x = null; foo(x); }  This program:  DOES compile OR does NOT compile --- Consider these two representations of a Result type that contains a value T if a computation succeeds, or an error E if it fails. struct Result1 { ok: Option, err: Option, } enum Result2 { Ok(T), Err(E) } The enum Result2 is considered more idiomatic than the struct Result1 in Rust. Which statement below is NOT a valid reason why?  - The struct could have ok and err both be None, while the enum must have at least one of them  - The struct is more syntactically verbose to construct than the enum  - The struct uses more space in memory at runtime than the enum  - The struct contains Option types, which are only intended to wrap structs --- __6.2__ Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. enum Location { Point(i32), Range(i32, i32) } fn main() { let l: Location = Location::Range(0, 5); let n = match l { Location::Point(_) => -1, Location::Range(_, n) => n, Location::Range(0, _) => 0, _ => -2 }; println!("{n}"); }  This program:  DOES compile OR does NOT compile --- Consider this method implemented for the Option type: impl Option { fn unwrap_or(self, other: T) -> T { match self { Some(t) => t, None => other } } } Which sentence best describes the behavior of this function?  - Returns a reference to the object inside self if it exists, and other otherwise  - Returns a new option containing the object inside self if it exists, and other otherwise  - Inserts other into self if self does not already contain a value  - Returns the object inside self if it exists, and other otherwise --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. #[derive(Debug)] enum Either { Left(usize), Right(String) } fn main() { let x = Either::Right(String::from("Hello world")); let value = match x { Either::Left(n) => n, Either::Right(s) => s.len() }; println!("{x:?} {value}"); }  This program:  DOES compile OR does NOT compile --- Consider these two implementations of a function to decrement an unsigned number twice. fn decr_twice_v1(n: u32) -> Option { match n { 0 => None, 1 => None, n2 => Some(n2 - 2) } } fn decr_twice_v2(n: u32) -> Option { if n == 0 { None } else if n == 1 { None } else { Some(n - 2) } } The functions have the same behavior for:   - Some, but not all inputs - No inputs - All inputs --- __6.3__ Which control flow construct would be most idiomatic to use in the following function? enum Location { Point(i32), Range(i32, i32) } fn print_range_max(loc: &Location) { // print the second field of Range, if loc is a Range } Response  - if let - match --- Which control flow construct would be most idiomatic to use in the following function? enum Location { Point(i32), Range(i32, i32) } fn get_start(loc: &Location) -> i32 { // return the first field of Range or the only field of Point } Response   - match - if let __6.4__ Ownership Inventory #1 (uses "in-browser IDE") -- geht bei mir nur in Firefox! A few important caveats about this experimental technology: PLATFORM COMPATIBILITY: the in-browser IDE does not work on touch-screens. The in-browser IDE has only been tested to work on Google Chrome 109 and Firefox 107. It definitely does not work in Safari. MEMORY USAGE: the in-browser IDE uses a WebAssembly build of rust-analyzer, which can take up a fair amount of memory. Each instance of the IDE appears to take around ~300 MB. (Note: we have also received some reports of >10GB memory usage.) SCROLLING: the in-browser IDE will "eat" your cursor if your cursor intersects with the editor while scrolling. If you're having trouble scrolling the page, try moving your cursor onto the rightmost scrollbar. LOAD TIMES: the IDE may take up to 15 seconds to initialize for a new program. It will say "Loading..." as you interact with code in the editor. --- Program: /// Makes a string to separate lines of text, /// returning a default if the provided string is blank fn make_separator(user_str: &str) -> &str { if user_str == "" { let default = "=".repeat(10); &default } else { user_str } } If you tried to compile this function, which of the following best describes the compiler error you would get?  - user_str does not live long enough - function make_separator cannot return two different references - function make_separator cannot return a reference of type &str - cannot return reference to local variable default --- Program: /// Makes a string to separate lines of text, /// returning a default if the provided string is blank fn make_separator(user_str: &str) -> &str { if user_str == "" { let default = "=".repeat(10); &default } else { user_str } } Normally if you try to compile this function, the compiler returns the following error: error[E0515]: cannot return reference to local variable `default` --> test.rs:6:9 | 6 | &default | ^^^^^^^^ returns a reference to data owned by the current function Assume that the compiler did NOT reject this function. Which (if any) of the following programs would (1) pass the compiler, and (2) possibly cause undefined behavior if executed? Check each program that satisfies both criteria, OR check "None of these programs" if none are satisfying. Response  - let s = make_separator("");  - let s = make_separator(""); println!("{s}");  - println!("{}", make_separator("Hello world!"));  - None of these programs --- Program /// Makes a string to separate lines of text, /// returning a default if the provided string is blank fn make_separator(user_str: &str) -> &str { if user_str == "" { let default = "=".repeat(10); &default } else { user_str } } Of the following fixes (highlighted in yellow), which fix best satisfies these three criteria: - The fixed function passes the Rust compiler, - The fixed function preserves the intention of the original code, and - The fixed function does not introduce unnecessary inefficiencies - fn make_separator(user_str: &str) -> String { if user_str == "" { let default = "=".repeat(10); default } else { user_str.to_string() } } - fn make_separator(user_str: &str) -> &str { if user_str == "" { let default = "=".repeat(10); &default } else { &user_str } } - fn make_separator(user_str: String) -> String { if user_str == "" { let default = "=".repeat(10); default } else { user_str } } --- /// Gets the string out of an option if it exists, /// returning a default otherwise fn get_or_default(arg: &Option) -> String { if arg.is_none() { return String::new(); } let s = arg.unwrap(); s.clone() } If you tried to compile this function, which of the following best describes the compiler error you would get? Response  - arg does not live long enough - cannot move out of arg in arg.unwrap()  - cannot return s.clone() which does not live long enough  - cannot call arg.is_none() without dereferencing arg --- /// Gets the string out of an option if it exists, /// returning a default otherwise fn get_or_default(arg: &Option) -> String { if arg.is_none() { return String::new(); } let s = arg.unwrap(); s.clone() } Normally if you try to compile this function, the compiler returns the following error: error[E0507]: cannot move out of `*arg` which is behind a shared reference --> test.rs:7:13 | 7 | let s = arg.unwrap(); | ^^^^-------- | | | | | `*arg` moved due to this method call | help: consider calling `.as_ref()` or `.as_mut()` to borrow the type's contents | move occurs because `*arg` has type `Option`, which does not implement the `Copy` trait Assume that the compiler did NOT reject this function. Which (if any) of the following programs would (1) pass the compiler, and (2) possibly cause undefined behavior if executed? Check each program that satisfies both criteria, OR check "None of these programs" if none are satisfying. - let opt = Some(String::from("Rust")); get_or_default(&opt);  - let opt = Some(String::from("Rust")); get_or_default(&opt); println!("{:?}", opt);  - None of these programs - let opt = Some(String::from("Rust")); let s = get_or_default(&opt); println!("{}", s); --- /// Gets the string out of an option if it exists, /// returning a default otherwise fn get_or_default(arg: &Option) -> String { if arg.is_none() { return String::new(); } let s = arg.unwrap(); s.clone() } Of the following fixes (highlighted in yellow), which fix best satisfies these three criteria: - The fixed function passes the Rust compiler, - The fixed function preserves the intention of the original code, and - The fixed function does not introduce unnecessary inefficiencies - fn get_or_default(arg: &mut Option) -> String { if arg.is_none() { return String::new(); } let s = arg.as_mut().unwrap(); s.clone() } - fn get_or_default(arg: &Option) -> String { match arg { None => String::new(), Some(s) => s.clone() } } - fn get_or_default(arg: &Option<&str>) -> String { if arg.is_none() { return String::new(); } let s = arg.unwrap(); s.to_string() } - fn get_or_default(arg: Option) -> String { if arg.is_none() { return String::new(); } let s = arg.unwrap(); s.clone() } Chapter 7 --------- Packages, Crates and Modules __7.1__ Which is the correct order, where "A > B" means "A contains B"? Response   - crate > package > module  - module > crate > package  - package > crate > module --- Imagine you see a Rust package foobar with the following files: foobar ├── Cargo.toml ├── build.rs └── src/ ├── main.rs ├── util.rs ├── lib.rs └── bin/ └── alt.rs How many crates does this package contain? Write your answer as a digit, e.g. 0, 1, and so on. Response Answer: _________ --- __7.2__ Which of the following is NOT a benefit of using modules? Response   - Modules boost the runtime performance of interdependent code within the same module  - Modules provide a scope to avoid naming conflicts across parts of a codebase  - Modules encapsulate implementation details that shouldn't be used by external clients  - Modules group related code so programmers can more easily work on a large codebase --- __7.3__ What is the keyword you use at the start of an absolute path to an item in the current crate?  _________ Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. pub mod foo { fn a() { println!("a"); } mod bar { pub fn b() { println!("b"); } } } fn main() { foo::bar::b(); } This program:  DOES compile OR does NOT compile --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. pub mod foo { pub mod bar { pub fn b() { println!("b"); } } pub fn a() { bar::b(); } } fn main() { foo::a(); } This program:  DOES compile OR does NOT compile --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. pub mod a { pub mod b { pub fn f() { println!("b1"); } pub mod c { pub fn f() { println!("c1"); } } } pub fn entry() { super::b::c::f(); } } pub mod b { pub fn f() { println!("b2"); } pub mod c { pub fn f() { println!("c2"); } } } fn main() { crate::a::entry(); } Response  This program:  DOES compile OR does NOT compile --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. pub mod point { #[derive(Debug)] pub struct Point(i32, i32); impl Point { pub fn origin() -> Self { Point(0, 0) } } } fn main() { let mut p = point::Point::origin(); p.0 += 1; println!("{p:?}"); } Response  This program:  DOES compile OR does NOT compile __7.4__ Which of the following statements best describes the function of the use keyword? - use allows access to items that circumvents normal privacy rules - use copies the contents of an item from one module to another - use indicates to the compiler that an item will be used and should be optimized - use reduces the verbosity of referring to items in the used path --- Consider this module and use statement: pub mod parent { pub fn a() {} fn b() {} pub mod child { pub fn c() {} } } fn main() { use parent::{*, child as alias}; // ... } Inside main, what is the total number of paths that can refer to a, b, or c (not including those that use self, super, or crate)? Write your answer as a digit such as 0 or 1. For example, if the only two valid paths were a and parent::b, then the answer would be 2. Answer: _____ __7.5__ Imagine a Rust package with the following directory structure: foobar ├── Cargo.toml └── src/ ├── lib.rs ├── engine.rs └── engine/ └── analysis.rs The contents of each file are: // engine/analysis.rs pub fn run() {} // engine.rs mod analysis; pub use analysis::*; // lib.rs pub mod engine; Say that another Rust developer is using the foobar library crate in a separate package, and they want to call the run function. What is the path they would write? Chapter 8 --------- Common Collections __8.1__ Which call to this find_until function will cause a runtime panic? fn find_until(v: &Vec, n: i32, til: usize) -> Option { for i in 0 .. til { if v[i] == n { return Some(i); } } return None; } Response  - find_until(&vec![1, 2, 3], 0, 0)  - find_until(&vec![1, 2, 3], 1, 4)  - find_until(&vec![1, 2, 3], 4, 4)  - find_until(&vec![1, 2, 3], 3, 3) --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn main() { let mut v = Vec::new(); let s = String::from("Hello "); v.push(s); v[0].push_str("world"); println!("original: {}", s); println!("new: {}", v[0]); } Response  This program:  DOES compile OR does NOT compile --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn main() { let v = vec![String::from("Hello ")]; let mut s = v[0]; s.push_str("world"); println!("{s}"); } Response  This program:  DOES compile OR does NOT compile --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn main() { let mut v = vec![1, 2, 3]; for i in &mut v { v.push(*i); } println!("{} {} {}", v[3], v[4], v[5]); } Response  This program:  DOES compile OR does NOT compile --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn main() { let mut v: Vec = vec![1, 2, 3]; let mut v2: Vec<&mut i32> = Vec::new(); for i in &mut v { v2.push(i); } *v2[0] = 5; let a = *v2[0]; let b = v[0]; println!("{a} {b}"); } Response  This program:  DOES compile OR does NOT compile __8.2__ What is the difference between using a + b and a.push_str(b) to concatenate two strings? Response  - There is no difference, they are aliases for the same function  - + consumes ownership of a, while push_str does not - push_str is more efficient at runtime than + - push_str consumes ownership of b, while + does not --- What is the maximum number of times a heap allocation could occur in this program? Write your answer in digits, e.g. 0 or 1. let s1 = String::from("tic"); let s2 = String::from("tac"); let s3 = String::from("toe"); let s = s1 + "-" + &s2 + "-" + &s3;  Answer: ________ --- Which statement is the best explanation for why Rust does not allow string indexing? Response   - Indexing strings is inefficient because string are null-terminated so their length cannot be efficiently computed  - Indexing strings would make Rust too easy to use, and Rust developers need job security  - Indexing strings is ambiguous because strings represent several granularities of sequenced data  - Indexing strings is unsafe because it can cause a segfault or buffer overflow --- Which statement best describes the difference between the types of a string slice &str and a byte slice &[u8]? Response   - &str always points to data stored in the program binary, whereas &[u8] can be stored anywhere in memory  - &str cannot be further sliced, while &[u8] can be sliced again  - &str can be constructed from a String, while &[u8] can only come from a Vec  - &str points to bytes that can always be interpreted as UTF-8, whereas &[u8] can be any byte sequence --- __8.3__ Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. use std::collections::HashMap; fn main() { let mut h = HashMap::new(); h.insert("k1", 0); let v1 = &h["k1"]; h.insert("k2", 1); let v2 = &h["k2"]; println!("{} {}", v1, v2); } Response  This program:  DOES compile OR does NOT compile --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. use std::collections::HashMap; fn main() { let mut h: HashMap> = HashMap::new(); for (i, c) in "hello!".chars().enumerate() { h.entry(c).or_insert(Vec::new()).push(i); } let mut sum = 0; for i in h.get(&'l').unwrap() { sum += *i; } println!("{}", sum); } Response  This program:  DOES compile OR does NOT compile __8.4__ Ownership Inventory #2 (inspired by common stackoverflow questions) /// Removes all the zeros in-place from a vector of integers. fn remove_zeros(v: &mut Vec) { for (i, t) in v.iter().enumerate().rev() { if *t == 0 { v.remove(i); v.shrink_to_fit(); } } } If you tried to compile this function, which of the following best describes the compiler error you would get? Response  - t cannot be dereferenced while i is live  - v does not live long enough to call v.remove(i)  - v.remove(i) cannot borrow v as mutable  - v.iter() cannot be called on a mutable reference --- /// Removes all the zeros in-place from a vector of integers. fn remove_zeros(v: &mut Vec) { for (i, t) in v.iter().enumerate().rev() { if *t == 0 { v.remove(i); v.shrink_to_fit(); } } } Normally if you try to compile this function, the compiler returns the following error: error[E0502]: cannot borrow `*v` as mutable because it is also borrowed as immutable --> test.rs:5:13 | 3 | for (i, t) in v.iter().enumerate().rev() { | -------------------------- | | | immutable borrow occurs here | immutable borrow later used here 4 | if *t == 0 { 5 | v.remove(i); | ^^^^^^^^^^^ mutable borrow occurs here Assume that the compiler did NOT reject this function. Which (if any) of the following programs would (1) pass the compiler, and (2) possibly cause undefined behavior if executed? Check each program that satisfies both criteria, OR check "None of these programs" if none are satisfying. - let mut v = vec![5, 5, 0]; remove_zeros(&mut v); println!("{:?}", v); - let mut v = vec![1, 2, 0, 3]; remove_zeros(&mut v);  - None of these programs - let mut v = vec![1; 100]; remove_zeros(&mut v); --- /// Removes all the zeros in-place from a vector of integers. fn remove_zeros(v: &mut Vec) { for (i, t) in v.iter().enumerate().rev() { if *t == 0 { v.remove(i); v.shrink_to_fit(); } } } Of the following fixes (highlighted in yellow), which fix best satisfies these three criteria: 1. The fixed function passes the Rust compiler, 2. The fixed function preserves the intention of the original code, and 3. The fixed function does not introduce unnecessary inefficiencies - fn remove_zeros(v: Vec) { for (i, t) in v.iter().enumerate().rev() { if *t == 0 { v.remove(i); v.shrink_to_fit(); } } } - fn remove_zeros(v: &mut Vec) { for (i, t) in v.clone().iter().enumerate().rev() { if *t == 0 { v.remove(i); v.shrink_to_fit(); } } } - fn remove_zeros(v: &mut Vec) { for i in (0 .. v.len()).rev() { if v[i] == 0 { v.remove(i); v.shrink_to_fit(); } } } - fn remove_zeros(v: &Vec) -> Vec { let mut new_vec = Vec::new(); for (i, t) in v.iter().enumerate().rev() { if *t != 0 { new_vec.push(*t) } } new_vec } --- // Reverses the elements of a vector in-place fn reverse(v: &mut Vec) { let n = v.len(); for i in 0 .. n / 2 { std::mem::swap(&mut v[i], &mut v[n - i - 1]); } } If you tried to compile this program, which of the following best describes the compiler error you would get? Response   - cannot borrow v as mutable twice for v[i] and v[n - i - 1] - cannot borrow v as immutable for v.len() when v is a mutable borrow  - cannot mutably borrow an element v[i] of a mutable vector --- /// Reverses the elements of a vector in-place fn reverse(v: &mut Vec) { let n = v.len(); for i in 0 .. n / 2 { std::mem::swap(&mut v[i], &mut v[n - i - 1]); } } Normally if you try to compile this function, the compiler returns the following error: error[E0499]: cannot borrow `*v` as mutable more than once at a time --> test.rs:5:40 | 5 | std::mem::swap(&mut v[i], &mut v[n - i - 1]); | -------------- - ^ second mutable borrow occurs here | | | | | first mutable borrow occurs here | first borrow later used by call Assume that the compiler did NOT reject this function. Which (if any) of the following programs would (1) pass the compiler, and (2) possibly cause undefined behavior if executed? Check each program that satisfies both criteria, OR check "None of these programs" if none are satisfying. - let mut v = vec![String::from("a"), String::from("b")]; reverse(&mut v); println!("{:?}", v);  - None of these programs  - let mut v = vec![String::from("a"), String::from("b")]; let x = &v[0]; reverse(&mut v); println!("{x}");  - let mut v = vec![String::from("a"), String::from("b")]; reverse(&mut v); --- // Reverses the elements of a vector in-place fn reverse(v: &mut Vec) { let n = v.len(); for i in 0 .. n / 2 { std::mem::swap(&mut v[i], &mut v[n - i - 1]); } } Of the following fixes (highlighted in yellow), which fix best satisfies these three criteria: 1. The fixed function passes the Rust compiler, 2. The fixed function preserves the intention of the original code, and 3. The fixed function does not introduce unnecessary inefficiencies - fn reverse(v: &mut Vec) { let n = v.len(); let mut v2 = v.clone(); for i in 0 .. n / 2 { std::mem::swap(&mut v[i], &mut v2[n - i - 1]); } } - fn reverse(v: &mut Vec) { let n = v.len(); for i in 0 .. n / 2 { let p1 = &mut v[i] as *mut String; let p2 = &mut v[n - i - 1] as *mut String; unsafe { std::ptr::swap_nonoverlapping(p1, p2, 1); } } } - fn reverse(v: &mut Vec) { let n = v.len(); for i in 0 .. n / 2 { let s1 = v[i].clone(); let s2 = v[n - i - 1].clone(); v[i] = s2; v[n - i - 1] = s1; } } - fn reverse(v: &Vec) -> Vec { let n = v.len(); let mut v2 = Vec::new(); for _ in 0 .. n { v2.push(v.pop().unwrap()); } v2 } Chapter 9 --------- Error Handling __9.1__ What is the name of the environment variable you should set to 1 to see the backtrace of a panic? Answer: ________ Which of the following is NOT a good reason to use a panic? - The program is about to perform a dangerous operation - The program has reached an unrecoverable error state - The program has reached an error state which should be communicated to a caller function - The program should stop executing as soon as possible __9.2__ Which of these statements best describes why File::open returns a Result and not an Option? - Because Result can represent why an operation failed, and file opening can fail for many reasons - Because Result represents the possibility of failure, while Option cannot represent failures - Because Result represents errors the same way as the underlying system calls - Because Result uses fewer bytes at runtime than Option to represent failures --- Given an arbitrary expression e of type Result, which code snippet best represents how e? is translated?Given an arbitrary expression e of type Result, which code snippet best represents how e? is translated?  - if let Err(err) = e { return Err(err); }  - e.unwrap()  - match e { Ok(x) => x, Err(err) => { return Err(err); } }  - match e { Ok(x) => x, Err(err) => panic!("{err}") } --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. // assume hello.txt has the contents "will" fn read_username_from_file() -> Option { let mut username_file = File::open("hello.txt")?; let mut username = String::new(); username_file.read_to_string(&mut username)?; Some(username) } fn main() { println!("{}", read_username_from_file().unwrap()); }  This program:  DOES compile OR does NOT compile __9.3__ A Rust programmer is designing a library for writing command-line interfaces. As a part of this library, they are implementing a function to parse command-line flags provided by a user. Which implementation would be most appropriate for this domain? fn parse_flag_v1(flag: &str) -> Result { match flag.strip_prefix("--") { Some(no_dash) => Ok(no_dash.to_string()), None => Err(format!("Invalid flag {flag}")) } } fn parse_flag_v2(flag: &str) -> String { match flag.strip_prefix("--") { Some(no_dash) => no_dash.to_string(), None => panic!("Invalid flag {flag}") } } Response  - parse_flag_v1 - parse_flag_v2 Chapter 10 ---------- Generic Types, Traits and Lifetimes __10.1__ Imagine using a third-party function whose implementation you don't know, but whose type signature is this: fn mystery(x: T) -> T { // ???? } Then you call mystery like this: let y = mystery(3); Assuming mystery uses no unsafe code, then the value of y must be: Answer: __________ --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn print_slice(v: &[T]) { for x in v { println!("{x}"); } } fn main() { print_slice(&[1, 2, 3]); } Response  This program: DOES compile OR does NOT compile Output: ? Hinweis: fn print_slice(v: &[T]) { --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. struct Point { x: T, y: T } impl Point { fn f(&self) -> &i32 { &self.y } } impl Point { fn f(&self) -> &T { &self.x } } fn main() { let p: Point = Point { x: 1, y: 2 }; println!("{}", p.f()); }  This program:  DOES compile OR does NOT compile __10.2__ Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. trait MakeNoise { fn make_noise(&self) { println!("(silence)"); } } struct Dog {} struct Cat {} impl MakeNoise for Dog { fn make_noise(&self) { println!("bark"); } } impl MakeNoise for Cat {} fn main() { let dog = Dog {}; let cat = Cat {}; dog.make_noise(); cat.make_noise(); } Does compile | Does not compile --- The following are statements about what kinds of trait implementations are permitted by Rust. Select each statement which is true. - You can implement an external trait for an external type - You can implement a local trait for a local type - You can implement a local trait for an external type - You can implement an external trait for a local type Loesung: The "orphan rule" requires that you cannot implement an external trait for an external type, to ensure code doesn't break if two crates provide conflicting implementations. __10.3__ Which kind of programming error is a lifetime supposed to prevent?  - Using a reference to an object after its memory has been freed - Using the result of a fallible computation before checking for the possibility of error - Not allocating enough memory for an object  - Indexing past the bounds of an array (buffer overflow) --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn shortest<'a, 'b>(x: &'a str, y: &'b str) -> &'a str { if x.len() < y.len() { x } else { y } } fn main() { println!("{}", shortest("hello", "rust")); }  This program:  DOES compile OR does NOT compile Context: If the type signature says that the function must return a reference with lifetime 'a, then it would be invalid to return a reference with a different lifetime 'b, i.e. y here. --- If a reference has a lifetime 'static, then this means: Response   - The data under the reference cannot be mutated  - The data under the reference is never deallocated  - The data under the reference is not dynamic  - The data under the reference lives in the static region of memory --- Consider the following un-annotated function signature. struct Foo<'a> { bar: &'a i32 } fn baz(f: Foo) -> &i32 { /* ... */ } Will Rust accept this function signature? If so, what lifetimes will it infer? - Rust will reject this function signature  - fn baz(f: Foo) -> &i32  - fn baz<'a>(f: Foo<'a>) -> &i32  - fn baz<'a>(f: Foo) -> &'a i32  - fn baz<'a>(f: Foo<'a>) -> &'a i32 --- Consider the following un-annotated function signature. struct Foo<'a> { bar: &'a i32 } // Foo changed to &Foo fn baz(f: &Foo) -> &i32 { /* ... */ } Will Rust accept this function signature? If so, what lifetimes will it infer? Response   - Rust will reject this function signature  - fn baz(f: &Foo) -> &i32  - fn baz<'a>(f: &Foo<'a>) -> &'a i32  - fn baz<'a>(f: &'a Foo) -> &'a i32  - fn baz<'a, 'b>(f: &'a Foo<'b>) -> &'a i32  - fn baz<'a, 'b>(f: &'a Foo<'b>) -> &'b i32 Mit der angegebenen Signatur wird es nicht kompiliert. Wenn man es wie folgt aendert kompiliert es! struct Foo<'a> { bar: &'a i32, } // Foo changed to &Foo // fn baz(f: &Foo) -> &i32 { fn baz<'a>(f: &'a Foo<'a>) -> &'a i32 { f.bar } fn main() { let a = 42; let F = Foo { bar: &a }; let r = baz(&F); println!("{r}"); } Abschliessend weitere 6 Fragen: Ownership Inventory #3 Program 1: 1 /// Returns the n-th largest element in a slice 2 fn find_nth(elems: &[T], n: usize) -> T { 3 elems.sort(); 4 let t = &elems[n]; 5 return t.clone(); 6 } If you tried to compile this program, which of the following best describes the compiler error you would get?  - cannot borrow elems as mutable for sort  - cannot move out of shared reference in expression &elems[n]  - cannot move out of shared reference for clone  - the lifetime of T must outlive &[T] --- Program 1: /// Returns the n-th largest element in a slice fn find_nth(elems: &[T], n: usize) -> T { elems.sort(); let t = &elems[n]; return t.clone(); } Normally if you try to compile this function, the compiler returns the following error: error[E0596]: cannot borrow `*elems` as mutable, as it is behind a `&` reference --> test.rs:3:5 | 3 | elems.sort(); | ^^^^^^^^^^^^ `elems` is a `&` reference, so the data it refers to cannot be borrowed a Assume that the compiler did NOT reject this function. Which (if any) of the following programs would (1) pass the compiler, and (2) possibly cause undefined behavior if executed? Check each program that satisfies both criteria, OR check "None of these programs" if none are satisfying. Response  - None of these programs -  let v = vec![5, 4, 3, 2, 1]; find_nth(&v, 0); println!("{}", v[0]);  - let v = vec![5, 4, 3, 2, 1]; let n = &v[0]; find_nth(&v, 0); println!("{}", n);  - let v = vec![5, 4, 3, 2, 1]; find_nth(&v, 10); --- /// Returns the n-th largest element in a slice fn find_nth(elems: &[T], n: usize) -> T { elems.sort(); let t = &elems[n]; return t.clone(); } Of the following fixes (highlighted in yellow), which fix best satisfies these three criteria: - The fixed function passes the Rust compiler, - The fixed function preserves the intention of the original code, and - The fixed function does not introduce unnecessary inefficiencies (die gelbe Einfaerbung fehlt leider) Response - fn find_nth(mut elems: Vec, n: usize) -> T { elems.sort(); let t = elems.remove(n); return t; } - fn find_nth(elems: &mut [T], n: usize) -> T { elems.sort(); let t = &elems[n]; return t.clone(); } - fn find_nth(elems: &[T], n: usize) -> T { let mut elems = elems.to_vec(); elems.sort(); let t = &elems[n]; return t.clone(); } - fn find_nth(elems: &[T], n: usize) -> T { let mut elem_refs: Vec<&T> = elems.iter().collect(); elem_refs.sort(); let t = elem_refs[n]; return t.clone(); } --- struct TestResult { /// Student's scores on a test scores: Vec, /// A possible value to curve all scores curve: Option } impl TestResult { pub fn get_curve(&self) -> &Option { &self.curve } /// If there is a curve, then increments all /// scores by the curve pub fn apply_curve(&mut self) { if let Some(curve) = self.get_curve() { for score in self.scores.iter_mut() { *score += *curve; } } } } If you tried to compile this program, which of the following best describes the compiler error you would get? Response   - in apply_curve, cannot borrow self as immutable for get_curve  - in get_curve, cannot return a reference to a local variable self.curve  - in apply_curve, cannot borrow self.scores as mutable for iter_mut  - in apply_curve, *score cannot be mutated --- struct TestResult { /// Student's scores on a test scores: Vec, /// A possible value to curve all scores curve: Option } impl TestResult { pub fn get_curve(&self) -> &Option { &self.curve } /// If there is a curve, then increments all /// scores by the curve pub fn apply_curve(&mut self) { if let Some(curve) = self.get_curve() { for score in self.scores.iter_mut() { *score += *curve; } } } } Normally if you try to compile this function, the compiler returns the following error: error[E0502]: cannot borrow `self.scores` as mutable because it is also borrowed as immutable --> test.rs:17:26 | 16 | if let Some(curve) = self.get_curve() { | ---------------- immutable borrow occurs here 17 | for score in self.scores.iter_mut() { | ^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here 18 | *score += *curve; | ------ immutable borrow later used here Assume that the compiler did NOT reject this function. Which (if any) of the following programs would (1) pass the compiler, and (2) possibly cause undefined behavior if executed? Check each program that satisfies both criteria, OR check "None of these programs" if none are satisfying. Response  - let mut result = TestResult { scores: vec![20, 50, 30], curve: Some(10) }; let x = &result.scores[0]; result.apply_curve(); println!("{}", x);  - None of these programs  - let mut result = TestResult { scores: vec![20, 50, 30], curve: Some(10) }; result.apply_curve(); println!("{:?}", result.scores);  - let mut result = TestResult { scores: vec![20, 50, 30], curve: Some(10) }; result.apply_curve(); --- struct TestResult { /// Student's scores on a test scores: Vec, /// A possible value to curve all scores curve: Option } impl TestResult { pub fn get_curve(&self) -> &Option { &self.curve } /// If there is a curve, then increments all /// scores by the curve pub fn apply_curve(&mut self) { if let Some(curve) = self.get_curve() { for score in self.scores.iter_mut() { *score += *curve; } } } } Of the following fixes (highlighted in yellow), which fix best satisfies these three criteria: 1. The fixed function passes the Rust compiler, 2. The fixed function preserves the intention of the original code, and 3. The fixed function does not introduce unnecessary inefficiencies Reponse - pub fn apply_curve(&mut self) { if let Some(curve) = self.get_curve().as_ref() { for score in self.scores.iter_mut() { *score += *curve; } } } - pub fn apply_curve(&mut self) { if let Some(curve) = self.get_curve() { for score in self.scores.iter() { *score += *curve; } } } - pub fn apply_curve(&mut self) { if let Some(curve) = self.get_curve() { for score in self.scores.clone().iter_mut() { *score += *curve; } } } - pub fn apply_curve(&mut self) { if let Some(curve) = self.curve { for score in self.scores.iter_mut() { *score += curve; } } } Chapter 11 ---------- Tests 11.1 What is the annotation you add to a function to indicate that it's a unit test? Answer: __________ Let's say you have a function with the type signature: fn f(x: usize) -> Result; And you want to test that f(0) should return Err(_). Which of the following is NOT a valid way to test that? Response  - #[test] fn test() { assert!(match f(0) { Ok(_) => false, Err(_) => true }); }  - #[test] #[should_panic] fn test() { f(0).unwrap(); } - #[test] #[should_err] fn test() -> Result { f(0) }  - #[test] fn test() { assert!(f(0).is_err()); } --- 11.2 When running cargo test with no additional configuration, which of the following actions may not work correctly if done by multiple tests? Response   - Writing text to a single file - Failing via panics - Logging strings to stdout  - Reading data from a single database --- Consider a program with the following unit test: #[test] fn test_the_logger() { /* ... */ } #[test] fn test_the_database() { /* ... */ } #[test] fn test_logger_and_database() { /* ... */ } What is the shortest string you can pass to cargo test such that only test_the_logger and test_the_database are executed? Response: _________ --- 11.3 Which of the following is NOT a good reason to wrap unit tests in #[cfg(test)] mod tests { ... }? Response  - It gives your tests access to private functions - It can reduce the size of generated compiler artifacts - It can improve compile times - It separates test helper functions from library code --- Chapter 12 ---------- Building a CLI tool Kein Quiz Chapter 13 ---------- Iterators and Closures __13.1__ Which of the following best describes the rationale for why Rust will infer the types of arguments/returns for closures, but not top-level functions? Response   - For backwards compatibility with older versions of Rust  - Anything assignable to a variable can be type-inferred, and top-level functions cannot be assigned to a variable  - Top-level functions can be part of a library's external interface, while closures cannot be directly exposed  - Due to the halting problem, it is mathematically impossible for Rust to infer the types of top-level functions --- Rust permits pattern matching within closure arguments, including the use of the underscore. For example, you could write the following: let f = |_| (); // sometimes called the "toilet closure" let s = String::from("Hello"); f(s); Which of the following best describes the relationship between f and s in this program? Response   - f has no effect on s  - f reads s and then throws away the result - f causes s to be immediately dropped - f captures s in its environment __13.2__ Which of the following best describes why iterators are described as "lazy"? Response  - An iterator takes ownership of the sequence of items it is iterating over  - An iterator will only iterate over a given number of items  - An iterator has no effect until you call methods that extract elements from the iterator  - An iterator creates a copy of each item it iterates over --- True/false: these two code snippets are semantically equivalent. Snippet 1: while let Some(x) = iter.next() { f(x); } Snippet 2: for x in iter { f(x); } Response   - False  - True --- Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn main() { let v = vec![1, 2, 3, 4]; let a: Vec<_> = v.iter().filter(|x: &&i32| *x % 2 == 0).map(|x: &i32| x * 2).collect(); let b: Vec<_> = v.iter().map(|x: &i32| x * 2).filter(|x: &i32| x % 2 == 0).collect(); println!("{} {}", a[0], b[0]); } This program:  DOES compile OR does NOT compile __13.3__ Kein Quiz __13.4__ Kein Quiz Chapter 14 ---------- More about Cargo and Crates.io Tests? Chapter 15 ---------- Smart Pointer __15.1__ Question 1 Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn main() { let mut n = 1; let b = Box::new(&mut n); **b += 1; println!("{}", n); }  This program:  DOES compile OR does NOT compile --- Question 2 Say we have a program with a variable: let x: [Box<(usize, usize)>; 4] = /* ... */ For a compile target with a 64-bit architecture, what is the minimum possible size in memory (in bytes) of x on the stack? Write your answer in digits, e.g. 0, 1, so on. Response: _________________ __15.2__ Question 1 Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. use std::ops::Deref; #[derive(Clone, Copy)] struct AccessLogger(i32); impl Deref for AccessLogger { type Target = i32; fn deref(&self) -> &Self::Target { println!("deref"); &self.0 } } fn main() { let n = AccessLogger(-1); let x = *n + 1; let n2 = n; println!("{} {}", x, *n) }  This program:  DOES compile OR does NOT compile __15.3__ Question 1 Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. struct Example(i32); impl Drop for Example { fn drop(&mut self) { self.0 += 1; println!("drop {}", self.0); } } fn main() { let e = Example(0); drop(e); drop(e); }  This program:  DOES compile OR does NOT compile Question 2 Consider this snippet that allocates a string: fn main() { let mut s = String::new(); _______________ } Which of the following are valid operations to fill in the underscore that would cause s to be dropped? - s.drop();  - (|_| ())(s); - { s }; - drop(s); __15.4__ Question 1 Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. use std::rc::Rc; fn main() { let n = Rc::new(1); let mut n2 = Rc::clone(&n); *n2 += 1; println!("{}", n); } This program: DOES compile OR does NOT compile Question 2 Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. use std::rc::Rc; struct Example; impl Drop for Example { fn drop(&mut self) { println!("drop"); } } fn main() { let x = Rc::new(Example); let y = Rc::clone(&x); println!("A"); drop(x); println!("B"); drop(y); println!("C"); }  This program:  DOES compile OR does NOT compile __15.5__ Question 1 Which of the following best describes the concept of interior mutability in Rust? - Allowing data on the inside of a data structure to be mutated  - Allowing data to be mutated through an immutable reference  - Wrapping unsafe code in a safe API - Using the borrow checker to enforce memory safety at runtime Question 2 Consider an API that tracks the number of calls to a particular method: struct Api { count: ??? } impl Api { fn some_method(&self) { // increment count // rest of the method... } } Say the count is represented as a usize. Which of the following would be the most appropriate wrapper type to use for this situation?  - Box - Rc  - None, usize is fine -RefCell Question 3 Consider the following incorrect implementation of a RefCell that does not check whether the interior value is borrowed: use std::cell::UnsafeCell; struct BadRefCell(UnsafeCell); impl BadRefCell { pub fn borrow_mut(&self) -> &mut T { unsafe { &mut *self.0.get() } } } Now say we have a BadRefCell like this: let v = BadRefCell(UnsafeCell::new(vec![1, 2, 3])); Which of the following snippets would violate memory safety using this API? - let v1 = v.borrow_mut(); let n = &v1[0]; v.borrow_mut().push(0); println!("{n}");  - let v1 = v.borrow_mut(); let v2 = v.borrow_mut(); v1.push(0); v2.push(0);  - drop(v.borrow_mut()); drop(v.borrow_mut());  - v.borrow_mut().push(0); let n = v.borrow_mut()[0]; println!("{n}"); __15.6__ Question 1 Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. use std::rc::Rc; fn main() { let r1 = Rc::new(0); let r4 = { let r2 = Rc::clone(&r1); Rc::downgrade(&r2) }; let r5 = Rc::clone(&r1); let r6 = r4.upgrade(); println!("{} {}", Rc::strong_count(&r1), Rc::weak_count(&r1)); }  This program:  DOES compile OR does NOT compile Chapter 16 ---------- Fearless concurrency __16.1__ Question 1 Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. use std::thread; fn main() { let mut n = 1; let t = thread::spawn(move || { n = n + 1; thread::spawn(move || { n = n + 1; }) }); n = n + 1; t.join().unwrap().join().unwrap(); println!("{n}"); } [ ] Does compile [ ] Does not compile Question 2 Consider this example from the text where a vector is improperly captured by a thread: use std::thread; fn main() { let v = vec![1, 2, 3]; let handle = thread::spawn(|| { println!("Here's a vector: {:?}", v); }); handle.join().unwrap(); } The Rust compiler includes this diagnostic: note: function requires argument type to outlive `'static` --> src/main.rs:6:18 | 6 | let handle = thread::spawn(|| { | __________________^ 7 | | println!("Here's a vector: {:?}", v); 8 | | }); | |______^ Recall that 'static is the lifetime of references that are valid for the entire program's duration. Which of the following best describes the note "function requires argument type to outlive 'static"?  - The closure passed to thread::spawn is not allowed to take any arguments - A closure may only capture string constants of type &'static str - The vector v contains values that do not outlive 'static - Rust doesn't know how long a thread will run, so the thread's captures must live forever __16.2__ Question 1 Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. use std::{sync::mpsc, thread}; enum ClientMessage { Incr, Get, Quit } enum ServerMessage { Get(usize) } fn main() { let (server_tx, client_rx) = mpsc::channel(); let (client_tx, server_rx) = mpsc::channel(); let server = thread::spawn(move || { let mut n = 0; loop { match server_rx.recv().unwrap() { ClientMessage::Quit => break, ClientMessage::Incr => n += 1, ClientMessage::Get => server_tx.send(ServerMessage::Get(n)).unwrap() } } }); for msg in [ClientMessage::Incr, ClientMessage::Get, ClientMessage::Quit] { client_tx.send(msg).unwrap(); } if let ServerMessage::Get(n) = client_rx.recv().unwrap() { println!("{}", n) } server.join().unwrap(); } 🐞 Response  This program:  DOES compileOR does NOT compile Question 2 Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. use std::{sync::mpsc, thread}; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let s = String::from("Hello world"); tx.send(s.clone()).unwrap(); tx.send(s.len()).unwrap(); }); let s = rx.recv().unwrap(); let n = rx.recv().unwrap(); println!("{s} {n}"); } 🐞 Response  This program:  DOES compileOR does NOT compile __16.3__ Question 1 In some concurrency APIs, a mutex is separate from the data it guards. For example, imagine a hypothetical Mutex API like this: let mut data = Vec::new(); let mx: Mutex = Mutex::new(); { let _guard = mx.lock(); data.push(0); } Which of the following best describes why Rust uses Mutex instead of just Mutex?  - To prevent accessing a mutex's data without locking the mutex - To prevent a mutex's data from being moved between threads - To require fewer calls to mutex methods - To improve the efficiency of concurrent programs with mutexes Question 2 Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. use std::{sync::Arc, thread}; fn main() { let s = String::from("Hello world"); let a = Arc::new(&s); let a2 = Arc::clone(&a); let t = thread::spawn(move || a2.len()); let len = t.join().unwrap(); println!("{} {}", a, len); } 🐞 Response  This program:  DOES compileOR does NOT compile __16.4__ Question 1 Imagine you are designing an API for a database connection like this: struct DbConnection { /* ... */ } impl DbConnection { fn query(&self) -> DbResult { /* ... */ } } Your database does not support concurrent queries from the same connection. Which of the following marker traits should DbConnection implement?  - Send and Sync - Sync - Neither Send nor Sync - Send Chapter 17 ---------- OO Features Chapter 18 ---------- Patterns and Matching Chapter 19 ---------- Advanced Features __19.1__ Question 1 Which of the following are "superpowers" that Rust enables inside an unsafe block? - Calling a function marked as unsafe - Disabling the borrow checker - Dereferencing a raw pointer - Converting a reference to a raw pointer Question 2 Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn main() { let mut v = Vec::with_capacity(4); for i in 0 .. 3 { v.push(i); } let n = &v[0] as *const i32; v.push(4); println!("{}", unsafe { *n }); }  This program:  DOES compile OR does NOT compile Question 3 Which of the following are situations where using unsafe code (or a safe wrapper around unsafe code) is an idiomatic method for working around the borrow checker? - Getting two mutable references to disjoint indices in an array - Returning a pointer to a stack-allocated variable out of a function - Having a reference to one field of a struct sit in another field of the same struct - Allowing values to be uninitialized when they are not being read __19.2__ Question 1 Recall the definition of the Add trait: trait Add { type Output; fn add(self, rhs: Rhs) -> Self::Output; } Which of the following best describes why Output is an associated type, while Rhs is a type parameter?  - A trait can only have a single associated type, so Rhs must be a type parameter to Add - An associated type cannot have a default, while a trait type parameter can have a default - A type T should be addable to many other types S, but a given T + S operation should always have a single output type - The add operation takes Rhs as input and produces Output as output, and type parameters are used for input while associated types are used for output Question 2 Recall the definition of the Add trait: trait Add { type Output; fn add(self, rhs: Rhs) -> Self::Output; } Which of the following best describes why Rhs is a type parameter to the trait Add rather than the function add? That is, why is Add not designed like this: trait Add { type Output; fn add(self, rhs: Rhs) -> Self::Output; }   - Code generation for the add function would require additional time for monomorphization due to the function-level type parameter - The type-checker executes more efficiently when Rhs is a trait-level type parameter than a function-level type parameter  - If Rhs were a function-level type parameter, then the definition of add could not assume any structure to Rhs  - Rhs cannot have a default type as a function-level type parameter Question 3 Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. mod inner { pub trait A { fn f(&self) -> usize { 0 } } pub trait B { fn f(&self) -> usize { 1 } } pub struct P; impl A for P {} impl B for P {} } fn main() { use inner::{P, B}; println!("{}", P.f()); }  This program:  DOES compile OR does NOT compile Question 4 Consider implementing a trait Trait for a type T. In which of the following situations do you need to wrap T in a newtype? - Trait is defined in an external crate and T is defined in an external crate - Trait is defined in the local crate and T is defined in the local crate - Trait is defined in an external crate and T is defined in the local crate - Trait is defined in the local crate and T is defined in an external crate __19.3__ Question 1 Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn expect_none(x: Option) -> ! { match x { Some(n) => panic!("Expected none, found Some({n})"), None => () } } fn main() { println!("{:?}", expect_none(None)); }  This program:  DOES compile OR does NOT compile Question 2 Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. fn is_equal(t1: &T, t2: &T) -> bool { t1 == t2 } fn main() { println!("{}", is_equal("Hello", "world")); }  This program:  DOES compileOR does NOT compile __19.4__ Question 1 Consider implementing a register function that takes a callback in two ways: fn register1(cb: fn(Event) -> ()); fn register2(cb: F) where F: Fn(Event) -> (); Which type signature permits register to take the widest variety of arguments? - register1 - register2 - they are equivalent __19.5__ Question 1 Determine whether the program will pass the compiler. If it passes, write the expected output of the program if it were executed. macro_rules! manylet { ( $( $i:ident ),* = $e:expr ) => { $( let mut $i = $e; )* } } fn main() { let mut s = String::from("A"); manylet!(x, y = s); x.push_str("B"); println!("{x}{y}"); }  This program:  DOES compile OR does NOT compile Question 2 Which of the following are valid reasons for implementing a macro as a procedural macro instead of a declarative macro? - You want to generate variable-length sequences of code - Your macro requires an entire item as input, not just an expression - You want to integrate with Rust's derive system - Your macro requires nontrivial analysis of the macro user's syntax Question 3 Which of the following best describes the input to a procedural macro? - The input is a sequence of tokens - The input is an abstract syntax tree - The input is a typed control-flow graph - The input is a byte-string [End]