Rain's Dev Diary - TheEvergreenStateCollege/upper-division-cs-23-24 GitHub Wiki
Question 1 What is the name of the command-line tool for managing the version of Rust on your machine?
Answer: rustup
Context: For example, you can write rustup update to get the latest version of Rust.
Question 1: Every executable Rust program must contain a function with the name:
Answer: main
Context: In your program, you add a main function like this:
fn main() {
// your code here
}
Question 2
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?
Answer: rustc generates a binary executable named hello
Context: Running rustc checks and compiles your program, but does not execute it.
Question 1: 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?
Answer: Cargo watches for file changes and re-executes the binary on a change
Context: Cargo does not watch your files by default. But you can use plugins like cargo-watch for this purpose.
Question 1 Which statement best describes what it means if a variable x is immutable?
Answer:x cannot be changed after being assigned a value.
Context: Immutable means "not mutable", or not changeable.
Question 2
What is the keyword used after let to indicate that a variable can be mutated?
Answer:mut
Context: For example, you can make a mutable variable x by writing: let mut x = 1.
Question 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 x = 1;
println!("{x}");
x += 1;
println!("{x}");
}
Answer:This program does not compile.
Context: This is a compiler error because line 4 tries to mutate x when x is not marked as mut.
Question 1 Which of the following statements is correct about the difference between using let and const to declare a variable?
Answer:const can be used in the global scope, and let can only be used in a function
Question 2: 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}");
}
Answer:
This program does compile.
The output of this program will be:2
Context: const variables are allowed to be defined outside of a function and to do limited forms of computation.
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 x: u32 = 1;
{
let mut x = x;
x += 2;
}
println!("{x}");
}
Answer:
This program does compile.
The output of this program will be: 1
Context: The statement x += 2 only affects the shadowed x inside the inner curly braces, not the outer x on line 2.
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 x: u32 = 1;
x = "Hello world";
println!("{x}");
}
Answer: This program does not compile.
Context: A variable cannot be assigned to a value of a different type than its original type.
Question 1 The largest number representable by the type i128 is:
Answer:2^127 - 1
Context: In general, a signed number with n bits can represent numbers between -(2^n - 1) and 2^n - 1 - 1.
Question 2
If x : u8 = 0, what will happen when computing x - 1?
Answer:It depends on the compiler mode.
Context: This expression will panic in debug mode and return 255 in release mode.
Question 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 x: fsize = 2.0;
println!("{x}");
}
Answer: This program does not compile.
Context: The type fsize does not exist. Floats must be either f32 or f64.
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 message = "The temperature today is:";
let x = [message, 100];
println!("{} {}", x[0], x[1]);
}
Answer: This program does not compile.
Context: An array can only contain elements of a single type. The syntax [message, 100] creates an array with two elements. The variable message is a string, and the constant 100 is a number.
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 t = ([1; 2], [3; 4]);
let (a, b) = t;
println!("{}", a[0] + t.1[0]);
}
Answer:This program does compile.
The output of this program will be:4
Context: The syntax [x; y] declares an array with y copies of the value x. The syntax (a, b) destructures t and binds a to [1; 2]. The syntax t.1 refers to the second element of t, which is [3; 4].
Question 1 The keyword for declaring a new function in Rust is:
Answer:fn
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 f(x) {
println!("{x}");
}
fn main() {
f(0);
}
Answer: This program does not compile.
Context: A function must declare the types of its parameters.
Question 1 In Rust, a curly-brace block like { /* ... */ } is:
An expression
A statement
A syntactic scope
Answer:1 and 3
Context: A block is an expression (#1) that is allowed to contain statements. It also defines a syntactic scope for let-bindings inside it (#3).
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 f(x: i32) -> i32 { x + 1 }
fn main() {
println!("{}", f({
let y = 1;
y + 1
}));
}
Answer: This program does compile.
The output of this program will be:3
Question 1 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;
}
Answer:True
Context: The first if-expression is a more concise way of representing the behavior of the second if-statement.
Note that Rust does not require x to be initially declared with let mut in the second snippet. This is because Rust can determine that x is only ever assigned once, since only one branch of the if-statement will ever execute.
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 x = 1;
let y = if x { 0 } else { 1 };
println!("{y}");
}
Answer:This program does not compile.
Context: The condition to an if-expression must be a boolean. Rust does not have a concept of "truthy" or "falsy" values.
Question 1 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;
}
}
Answer:True
Context: It will in fact terminate after the first iteration of the loop.
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 a = [5; 10];
let mut sum = 0;
for x in a {
sum += x;
}
println!("{sum}");
}
Answer:This program does compile.
The output of this program will be:50
Context: The array a has the element 5 repeated 10 times, therefore its sum is 50. The answer is not 15 --- note that the syntax [5; 10] is different from [5, 10].
Question 1 Which of the following best describes the difference between the stack and the heap?
Answer: The stack holds data associated with a specific function, while the heap holds data that can outlive a function
Context: Frames in the stack are associated with a specific function, and are deallocated when the function returns. Data on the heap can live indefinitely. Note that both stack and heap data can be mutable and can be copyable. The heap is allowed to contain pointers (even to the stack, as we will see later).
Question 2 Consider the execution of the following snippet, with the final state shown:
In the final state, how many copies of the number 15 live anywhere in memory? Write your answer as a digit, such as 0 or 1.
Answer: 2
Question 1 Which of the following is NOT a kind of undefined behavior?
Answer: Having a pointer to freed memory in a stack frame
Context: It can be perfectly safe to have a pointer to freed memory in a stack frame. The important thing is to not use that pointer again, e.g. by reading it or freeing it.
Question 2Determine 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);
}
Answer:This program does compile.
The output of this program will be: hello world
Context: This program is valid because s is not used after moving it into add_suffix.
Question 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 s = String::from("hello");
let s2;
let b = false;
if b {
s2 = s;
}
println!("{}", s);
}
Answer:This program does not compile.
Context: Because s could be moved inside of the if-statement, it is illegal to use it on line 8. While the if-statement will never execute in this program because b is always false, Rust does not in general try to determine whether if-statements will or won't execute. Rust just assumes that it might be executed, and therefore s might be moved.
Question 4 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 these snippets" if none of these snippets would cause undefined behavior.
Answer:
let b = Box::new(0);
let b2 = b;
move_a_box(b);
let b = Box::new(0);
move_a_box(b);
println!("{}", b);
let b = Box::new(0);
move_a_box(b);
let b2 = b;
Context: The key idea is that when a box is passed to move_a_box, its memory is deallocated after move_a_box ends. Therefore:
- Reading b via println after move_a_box is undefined behavior, as it reads freed memory.
- Giving b a second owner is undefined behavior, as it would cause Rust to free the box a second time on behalf of b2. It doesn't matter whether the let b2 = b binding happens before or after move_a_box.
However, doing let b2 = b and then println is not undefined behavior. Although b is moved, its data is not deallocated until move_a_box is called at the end. Therefore this program is technically safe, although still rejected by Rust.
Question 1 Consider the following program, showing the state of memory after the last line:
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.
Answer: 3
Context: ***y is the correct expression. y has the type Box<&Box>. It is a heap pointer to a stack reference to a heap pointer. Therefore y must be dereferenced three times, once for each layer of indirection.
Question 2
Consider the following program, showing the state of memory after the last line:
Which of the following best explains why v is not deallocated after calling get_first?
Answer:vr is a reference which does not own the vector it points to
Context: References are non-owning pointers. Therefore passing &v to get_first does not move ownership of v into get_first, and subsequently v is not deallocated after get_first ends.
Questions
-
If a value is Copyable (implementing the Copy trait) or being a primitive type, can it always be moved (ownership can be transferred in a function call)? yes
-
Can we fix the Move/Copy error by passing a reference? no
-
If so, how would you change the code below to use references? make a mutable reference