Quizfragen zu “The Experimental Rust Book”

Systemnahe Programmierung mit Rust

https://rust-book.cs.brown.edu

Kurs: https://tha.de/~hhoegl/home/SysProgRust

H. Högl, 📧

Letzter Update: 2024-11-05

Die Abschnitte 6 bis 19 sind noch in Bearbeitung.

Chapter 1

Getting Started

Chapter 2

A Guessing Game

No Quiz

Chapter 3

Common Programming Concepts

Which statement best describes what it means if a variable x is immutable?

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. Output: __________
( ) does NOT compile


Which of the following statements is correct about the difference between using let and const to declare a variable?


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
( ) 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. Output __________
( ) 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 compile. Output __________
( ) does NOT compile


The largest number representable by the type i128 is:


if x : u8 = 0, what will happen when computing x - 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 x: fsize = 2.0;
    println!("{x}");
}

This program:

( ) DOES compile: Output __________
( ) 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 compile: Output __________
( ) 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: Output __________
( ) 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: Output __________
( ) does NOT compile


In Rust, a curly-brace block like { ... } is:


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: Output __________
( ) 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!)


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: Output __________
( ) 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;
    }
}

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: Output __________
( ) does NOT compile

Chapter 4

4.1 Understanding Ownership

Which of the following is NOT a kind of undefined behavior?


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. Output: ___________
() 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. Output: __________
() does NOT compile


Say we have a function that moves a box, like this:

fn move_a_box(b: Box<i32>) {
    // 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.

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.

Response: ___________


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?


Consider the permissions in the following program:

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.


Consider the permissions in the following program:

Which of the following best explains why strs loses and regains write permissions?


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


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. Output: __________ () 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. Output __________ () 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<i32>, 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<i32>, n: i32) -> i32 {
| --------- help: consider changing this to be a mutable reference: `&mut Vec<i32>`
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” .

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?


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.


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<f32>) {
    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.


Which of the following best explains why an i32 can be copied without a move, but a String cannot?


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 following program does not compile:

fn copy_to_prev(v: &mut Vec<i32>, 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


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


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. Output: __________
() 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[..];
}

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. Output: __________
() 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?


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?


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?


Consider the permissions in the following program:

Which of the following best explains why *s_ref does not have the O (own) permission?


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:


fn extract(b: &Box<i32>) -> i32 {
    let b2: Box<i32> = *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.


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

Chapter 5

Using Structs

5.1 Defining and instantiating structs

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. Output __________
( ) 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 compile. Output: __________
( ) does NOT compile

5.2 An example program using structs

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. Output: __________
( ) does NOT compile


Which statement best describes a difference between the Display and Debug traits?

5.3 Method Syntax

What is the keyword for constructor functions in Rust?


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. Output: __________
() does NOT compile


Say you have a variable v of type &mut Vec<i32>, and you want to call the len method with the following signature:

impl Vec<i32> {
    fn len(&self) -> usize {
        /* ... */
    }
}

If you try to compile the expression v.len(), which of the following statements best describes what happens?


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;
    }
}

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. Output: __________
( ) 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. Output: __________
( ) does NOT compile