Skip to content
Learn Rust
Theme

Checkpoint: Syntax

Seven short programs, one question each. For every one, decide before you open the answer: does it compile, and what does it print? If it does not compile, which error code does the compiler give? The questions use only what the six Syntax lessons teach.

RememberPredict first, then look. A wrong guess teaches more than a right one, because the compiler’s message names the rule you missed.

Each program is a file in a Cargo project (src/bin/q1.rs and so on) and runs with cargo run --bin q1. The answers are real runs in the page’s image (Rust 1.99.1, edition 2024).

Rustsrc/bin/q1.rs
fn main() {
let x = 5;
let x = {
let x = x * 2;
x + 1
};
let y = if x > 10 { "big" } else { "small" };
let z = {
let _unused = x + 1;
};
println!("{x} {y} {z:?}");
}
Answer
terminal
$ cargo run --bin q1
11 big ()

A block is worth its last line when that line has no semicolon, so the block gives x the value 11. The x inside the block is a new variable that hides the outer one inside the block. The second block ends in a statement, so it is worth (), which {:?} prints as (). See The semicolon decides and shadowing.

Rustsrc/bin/q2.rs
fn plus_one(n: i32) -> i32 {
n + 1;
}
fn main() {
println!("{}", plus_one(4));
}
Answer
$ cargo run --bin q2
error[E0308]: mismatched types
 --> src/bin/q2.rs:1:24
  |
1 | fn plus_one(n: i32) -> i32 {
  |    --------            ^^^ expected `i32`, found `()`
  |    |
  |    implicitly returns `()` as its body has no tail or `return` expression
2 |     n + 1;
  |          - help: remove this semicolon to return this value

n + 1; is a statement, so the body has no value and is worth (), but the signature promises an i32. The compiler points at the semicolon. See The semicolon decides.

Rustsrc/bin/q3.rs
fn main() {
let a = 300_i32 as u8;
let b = -1_i32 as u8;
let c = 3.99_f64 as i32;
let d = -5.5_f64 as u32;
let e = (b'a' + 2) as char;
println!("{a} {b} {c} {d} {e}");
}
Answer
terminal
$ cargo run --bin q3
44 255 3 0 c

as does not fail at run time, so it can lose information silently. An integer that does not fit keeps its low bits (300 becomes 44, and -1 becomes 255 in a u8). A float is cut toward zero and stops at the limits of the target type, so -5.5 becomes 0 in a u32. b'a' + 2 is the byte 99, which as char turns into c. See as and the turbofish.

Rustsrc/bin/q4.rs
fn main() {
let n = "42".parse().unwrap();
println!("{}", n);
}
Answer
$ cargo run --bin q4
error[E0284]: type annotations needed
 --> src/bin/q4.rs:2:9
  |
2 |     let n = "42".parse().unwrap();
  |         ^        ----- type must be known at this point
  |
  = note: cannot satisfy `<_ as FromStr>::Err == _`
  = note: the type must also implement `FromStr`
help: consider giving `n` an explicit type
  |
2 |     let n: /* Type */ = "42".parse().unwrap();
  |          ++++++++++++

  ... (notes pointing into the standard library trimmed)

In this version of Rust it is E0284, not the E0282 you may have guessed. parse can build any type that knows how to be made from text (the trait is called FromStr), and {} prints any type that can be displayed, so nothing says which type n is. Name it with the turbofish, parse::<i32>(), or with let n: i32. The first form, run:

terminal
$ cargo run --bin q4
42

See as and the turbofish.

Rustsrc/bin/q5.rs
fn longer(a: &str, b: &str) -> &str {
if a.len() >= b.len() { a } else { b }
}
fn main() {
println!("{}", longer("tiger", "owl"));
}
Answer
$ cargo run --bin q5
error[E0106]: missing lifetime specifier
 --> src/bin/q5.rs:1:32
  |
1 | fn longer(a: &str, b: &str) -> &str {
  |              ----     ----     ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b`
help: consider introducing a named lifetime parameter
  |
1 | fn longer<'a>(a: &'a str, b: &'a str) -> &'a str {
  |          ++++     ++          ++          ++

The function takes two borrowed strings and returns a borrowed one. Looking only at the signature, the compiler cannot tell which input the result borrows from, so you have to say it with a lifetime name. The help: line in the output shows where the names go: after the function name, and on each & that should share the lifetime. A function with one borrowed input needs no name, because the compiler assumes the result borrows from that one input; the lesson explains when it fills the names in for you. See 'a: why it appears.

This does not compile. What is the error code, and what do you change?

Rustsrc/bin/q6.rs
fn largest<T>(items: &[T]) -> &T {
let mut best = &items[0];
for item in items {
if item > best {
best = item;
}
}
best
}
fn main() {
println!("{}", largest(&[3, 9, 4]));
}
Answer
$ cargo run --bin q6
error[E0369]: binary operation `>` cannot be applied to type `&T`
 --> src/bin/q6.rs:4:17
  |
4 |         if item > best {
  |            ---- ^ ---- &T
  |            |
  |            &T
  |
help: consider restricting type parameter `T` with trait `PartialOrd`
  |
1 | fn largest<T: std::cmp::PartialOrd>(items: &[T]) -> &T {
  |             ++++++++++++++++++++++

> needs a type that can be ordered, and a bare T promises nothing, so the check happens where you write the function, not where you call it. Add the bound the compiler suggests:

Rustq6.rs
fn largest<T: PartialOrd>(items: &[T]) -> &T {
let mut best = &items[0];
for item in items {
if item > best {
best = item;
}
}
best
}
fn main() {
println!("{}", largest(&[3, 9, 4]));
}
$ cargo run --bin q6
9

See The bound is checked where you write it.

Rustsrc/bin/q7.rs
fn main() {
let w = 6;
println!("[{:>w$}] [{:<5}] [{:^7.2}] [{:#x}] [{:05}]", "ab", 42, 3.14159, 255, -42);
println!("{{{}}} {0}-{0}", 7);
}
Answer
terminal
$ cargo run --bin q7
[    ab] [42   ] [ 3.14  ] [0xff] [-0042]
{7} 7-7

{:>w$} takes its width from the variable w. Text is left-aligned unless you ask, so > is what right-aligns ab, and {:<5} moves the number to the left. .2 keeps two decimals and ^ centres, with the odd space on the right. {:#x} adds the 0x, and {:05} pads with zeros after the sign. {{ and }} print braces, and {0} is the first argument again. See Format strings.

Count a question as right when you gave the correct output, or the correct error code and the reason. The reason matters more than the number: a guess of E0282 for question 4 is close, and the compiler’s note (“cannot satisfy <_ as FromStr>::Err == _”) shows why it chose a different code. The same program in another language might run, so a miss here is usually a Rust rule you have not met yet, not a gap in how you program.

Read the failing answers’ error text slowly. Each message follows one pattern: the code and a one-line summary, the source line with arrows, and, usually, a help: line that is often the fix. Reading that shape fluently is worth practising.

When all seven felt easy, go on to Checkpoint: Basics.