Skip to content
Learn Rust
Theme

What Rust doesn't have

Syntax · Lesson 4

Some things you reach for by habit do not exist in Rust: x++, a ? b : c, null, C-style for, default arguments, classes, exceptions, goto. Each one fails with a compiler message, and each has a Rust way. This page lists them, with the real error and what to write instead.

Coming from Goi++ is a statement in Go, and Rust has no ++ at all. Much else matches: Go has no ternary operator, no function overloading by type, no default arguments and no mixing of typed number types either. What you give up is nil, goto, defer, for cond {} and the if err != nil boilerplate: Rust has Option, labeled break, Drop, while and ?. Watch the break inside a switch: in Rust it leaves the loop.

Coming from KotlinYou are used to a lot of this already: if is an expression, there is no ternary operator, and T? is a checked null. What Rust takes away is ++, ?., ?: and !! (Option has map, unwrap_or and unwrap), default and named arguments, open classes with inheritance, and exceptions (Result and ? replace try/catch).

Coming from PythonPython has no x++ either, so x += 1 is already your habit. What is new is that an if needs a bool (an empty list or a 0 is not false), a call has no keyword arguments and a definition has no default values, // starts a comment, and/or/not/elif are &&/||/!/else if, a name has one type, and braces replace the indentation.

Coming from TypeScriptx++, a ? b : c, null and undefined, ??, truthiness (if (name)), throw and optional parameters all have a different Rust form: x += 1, if a { b } else { c }, Option, unwrap_or, an explicit test, Result and ?, and a struct. Careful: u?.name is valid Rust, and it is not a safe call but an early return. Rust also does not turn a number into another number type for you.

Coming from Javai++, ?:, null, new, this, extends, function overloading, switch that falls through, finally and checked or unchecked exceptions all have a different Rust form. Java widens int to long on its own; Rust makes you write the conversion. A break in a switch inside a loop leaves the switch in Java; in Rust it leaves the loop.

Coming from C#i++, ?:, ?., new, this, : BaseClass, function overloads, optional and named arguments, and try/catch have a different Rust form, and null becomes Option. C# widens int to long on its own; Rust makes you write the conversion. A break in a switch inside a loop leaves the switch in C#; in Rust it leaves the loop.

Coming from Ci++, ?:, for (i = 0; i < n; i++), NULL, a switch that falls through, goto, (double)n, -> and #define do not exist or fail in Rust, as does if (n) on a number. A global you assign without unsafe fails to compile too, and C's silent number conversions (int n = 3.9;) need an explicit as. A break; at the end of a ported case compiles and leaves the loop.

Coming from C++Everything in the C list, plus classes with inheritance, function overloads, default arguments, constructors and exceptions: Rust has none of them. A struct holds data, an impl block holds methods, a trait shares behaviour (and impl Add is your operator+), a Drop runs where a destructor would, and a Result carries a failure. auto is let.

The quick comparison

Rustsrc/main.rsRun ↗
// What other languages write with ?:, i++, null and switch, written in Rust.
fn size(n: i32) -> &'static str {
match n {
1 | 2 => "small",
3 => "medium",
_ => "large",
}
}
fn first_even(items: &[i32]) -> Option<i32> {
for &x in items {
if x % 2 == 0 {
return Some(x);
}
}
None
}
fn main() {
let mut total = 0;
for i in 0..4 {
total += i;
}
let label = if total > 5 { "big" } else { "small" };
println!("total: {total} ({label})");
println!("size: {} {}", size(2), size(9));
match first_even(&[3, 5, 8]) {
Some(n) => println!("first even: {n}"),
None => println!("no even number"),
}
println!("none: {}", first_even(&[1, 3]).unwrap_or(-1));
let wide = total as i64 * 1_000_000_000;
println!("wide: {wide}");
}
$ cargo run
total: 6 (big)
size: small large
first even: 8
none: -1
wide: 6000000000
package main
import "fmt"
// What other languages write with ?:, i++, null and switch, written in Go.
func size(n int) string {
switch n {
case 1, 2:
return "small"
case 3:
return "medium"
default:
return "large"
}
}
func firstEven(items []int) (int, bool) {
for _, x := range items {
if x%2 == 0 {
return x, true
}
}
return 0, false
}
func main() {
total := 0
for i := 0; i < 4; i++ {
total += i
}
label := "small"
if total > 5 {
label = "big"
}
fmt.Printf("total: %d (%s)\n", total, label)
fmt.Printf("size: %s %s\n", size(2), size(9))
if n, ok := firstEven([]int{3, 5, 8}); ok {
fmt.Printf("first even: %d\n", n)
} else {
fmt.Println("no even number")
}
n, ok := firstEven([]int{1, 3})
if !ok {
n = -1
}
fmt.Printf("none: %d\n", n)
wide := int64(total) * 1_000_000_000
fmt.Printf("wide: %d\n", wide)
}
$ go run main.go
total: 6 (big)
size: small large
first even: 8
none: -1
wide: 6000000000
// What other languages write with ?:, i++, null and switch, written in Kotlin.
fun size(n: Int): String = when (n) {
1, 2 -> "small"
3 -> "medium"
else -> "large"
}
fun firstEven(items: List<Int>): Int? {
for (x in items) {
if (x % 2 == 0) return x
}
return null
}
fun main() {
var total = 0
for (i in 0 until 4) {
total += i
}
val label = if (total > 5) "big" else "small"
println("total: $total ($label)")
println("size: ${size(2)} ${size(9)}")
val even = firstEven(listOf(3, 5, 8))
if (even != null) println("first even: $even") else println("no even number")
println("none: ${firstEven(listOf(1, 3)) ?: -1}")
val wide = total.toLong() * 1_000_000_000
println("wide: $wide")
}
$ kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar
total: 6 (big)
size: small large
first even: 8
none: -1
wide: 6000000000
# What other languages write with ?:, i++, null and switch, written in Python.
from typing import Optional
def size(n: int) -> str:
if n in (1, 2):
return "small"
if n == 3:
return "medium"
return "large"
def first_even(items: list[int]) -> Optional[int]:
for x in items:
if x % 2 == 0:
return x
return None
total = 0
for i in range(4):
total += i
label = "big" if total > 5 else "small"
print(f"total: {total} ({label})")
print(f"size: {size(2)} {size(9)}")
even = first_even([3, 5, 8])
if even is not None:
print(f"first even: {even}")
else:
print("no even number")
none = first_even([1, 3])
print(f"none: {-1 if none is None else none}")
wide = total * 1_000_000_000
print(f"wide: {wide}")
$ python3 main.py
total: 6 (big)
size: small large
first even: 8
none: -1
wide: 6000000000
// What other languages write with ?:, i++, null and switch, written in TypeScript.
function size(n: number): string {
switch (n) {
case 1:
case 2:
return "small";
case 3:
return "medium";
default:
return "large";
}
}
function firstEven(items: number[]): number | undefined {
for (const x of items) {
if (x % 2 === 0) return x;
}
return undefined;
}
let total = 0;
for (let i = 0; i < 4; i++) {
total += i;
}
const label = total > 5 ? "big" : "small";
console.log(`total: ${total} (${label})`);
console.log(`size: ${size(2)} ${size(9)}`);
const even = firstEven([3, 5, 8]);
if (even !== undefined) {
console.log(`first even: ${even}`);
} else {
console.log("no even number");
}
console.log(`none: ${firstEven([1, 3]) ?? -1}`);
const wide = total * 1_000_000_000;
console.log(`wide: ${wide}`);
$ tsc --strict --module nodenext --types node main.ts && node main.js
total: 6 (big)
size: small large
first even: 8
none: -1
wide: 6000000000
import java.util.List;
import java.util.Optional;
public class Main {
// What other languages write with ?:, i++, null and switch, written in Java.
static String size(int n) {
return switch (n) {
case 1, 2 -> "small";
case 3 -> "medium";
default -> "large";
};
}
static Optional<Integer> firstEven(List<Integer> items) {
for (int x : items) {
if (x % 2 == 0) {
return Optional.of(x);
}
}
return Optional.empty();
}
public static void main(String[] args) {
int total = 0;
for (int i = 0; i < 4; i++) {
total += i;
}
String label = total > 5 ? "big" : "small";
System.out.println("total: " + total + " (" + label + ")");
System.out.println("size: " + size(2) + " " + size(9));
Optional<Integer> even = firstEven(List.of(3, 5, 8));
if (even.isPresent()) {
System.out.println("first even: " + even.get());
} else {
System.out.println("no even number");
}
System.out.println("none: " + firstEven(List.of(1, 3)).orElse(-1));
long wide = (long) total * 1_000_000_000;
System.out.println("wide: " + wide);
}
}
$ javac Main.java && java Main
total: 6 (big)
size: small large
first even: 8
none: -1
wide: 6000000000
// What other languages write with ?:, i++, null and switch, written in C#.
static string Size(int n) => n switch
{
1 or 2 => "small",
3 => "medium",
_ => "large",
};
static int? FirstEven(int[] items)
{
foreach (var x in items)
{
if (x % 2 == 0) return x;
}
return null;
}
var total = 0;
for (var i = 0; i < 4; i++)
{
total += i;
}
var label = total > 5 ? "big" : "small";
Console.WriteLine($"total: {total} ({label})");
Console.WriteLine($"size: {Size(2)} {Size(9)}");
var even = FirstEven(new[] { 3, 5, 8 });
if (even is not null)
{
Console.WriteLine($"first even: {even}");
}
else
{
Console.WriteLine("no even number");
}
Console.WriteLine($"none: {FirstEven(new[] { 1, 3 }) ?? -1}");
long wide = (long)total * 1_000_000_000;
Console.WriteLine($"wide: {wide}");
$ dotnet run
total: 6 (big)
size: small large
first even: 8
none: -1
wide: 6000000000
#include <stdio.h>
// What other languages write with ?:, i++, null and switch, written in C.
const char *size(int n) {
switch (n) {
case 1:
case 2:
return "small";
case 3:
return "medium";
default:
return "large";
}
}
// Returns a pointer to the first even number, or NULL when there is none.
const int *first_even(const int *items, int len) {
for (int i = 0; i < len; i++) {
if (items[i] % 2 == 0) {
return &items[i];
}
}
return NULL;
}
int main(void) {
int total = 0;
for (int i = 0; i < 4; i++) {
total += i;
}
const char *label = total > 5 ? "big" : "small";
printf("total: %d (%s)\n", total, label);
printf("size: %s %s\n", size(2), size(9));
int a[] = {3, 5, 8};
const int *even = first_even(a, 3);
if (even != NULL) {
printf("first even: %d\n", *even);
} else {
printf("no even number\n");
}
int b[] = {1, 3};
const int *none = first_even(b, 2);
printf("none: %d\n", none != NULL ? *none : -1);
long long wide = (long long)total * 1000000000LL;
printf("wide: %lld\n", wide);
return 0;
}
$ gcc -Wall -Wextra main.c -o main && ./main
total: 6 (big)
size: small large
first even: 8
none: -1
wide: 6000000000
#include <iostream>
#include <optional>
#include <vector>
// What other languages write with ?:, i++, null and switch, written in C++.
const char *size(int n) {
switch (n) {
case 1:
case 2:
return "small";
case 3:
return "medium";
default:
return "large";
}
}
std::optional<int> first_even(const std::vector<int> &items) {
for (int x : items) {
if (x % 2 == 0) {
return x;
}
}
return std::nullopt;
}
int main() {
int total = 0;
for (int i = 0; i < 4; i++) {
total += i;
}
const char *label = total > 5 ? "big" : "small";
std::cout << "total: " << total << " (" << label << ")\n";
std::cout << "size: " << size(2) << " " << size(9) << "\n";
auto even = first_even({3, 5, 8});
if (even) {
std::cout << "first even: " << *even << "\n";
} else {
std::cout << "no even number\n";
}
std::cout << "none: " << first_even({1, 3}).value_or(-1) << "\n";
long long wide = static_cast<long long>(total) * 1000000000LL;
std::cout << "wide: " << wide << "\n";
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main
total: 6 (big)
size: small large
first even: 8
none: -1
wide: 6000000000

What's different

  • Rust has no x++ and no a ? b : c. Write x += 1, and if a { b } else { c }, which is an expression.
  • There is no null. A value that may be missing has the type Option<T>, and the compiler makes you handle None.
  • Numbers do not convert on their own: i32 plus i64 is an error. Write as, From or try_from.
  • No switch fallthrough, no exceptions for ordinary errors, no default or named arguments, no function overloading by parameter type, no classes and no goto. Each has a Rust form, shown on this page. A break in a match inside a loop leaves the loop.
Gotcha

Writing x++

Looks fine: in Go, Kotlin, TypeScript, Java, C#, C and C++ it runs fine. Rust won't compile it.

Works in Go. In Rust it's just a rule.Works in Kotlin. In Rust it's just a rule.Python refuses this too. Same idea, different words.Works in TypeScript. In Rust it's just a rule.Works in Java. In Rust it's just a rule.Works in C#. In Rust it's just a rule.Works in C. In Rust it's just a rule.Works in C++. In Rust it's just a rule.
Rustsrc/main.rs✗ won't compile
fn main() {
let mut count = 0;
for _ in 0..3 {
count++;
}
println!("count: {count}");
}
package main
import "fmt"
func main() {
count := 0
for i := 0; i < 3; i++ {
count++
}
fmt.Printf("count: %d\n", count)
}
$ go run main.go✓ runs
count: 3
fun main() {
var count = 0
for (i in 0 until 3) {
count++
}
println("count: $count")
}
$ kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar✓ runs
count: 3
count = 0
for _ in range(3):
count++
print(f"count: {count}")
$ python3 main.py✗ refuses too
  File "main.py", line 3
    count++
           ^
SyntaxError: invalid syntax
let count = 0;
for (let i = 0; i < 3; i++) {
count++;
}
console.log(`count: ${count}`);
$ tsc --strict --module nodenext --types node main.ts && node main.js✓ runs
count: 3
public class Main {
public static void main(String[] args) {
int count = 0;
for (int i = 0; i < 3; i++) {
count++;
}
System.out.println("count: " + count);
}
}
$ javac Main.java && java Main✓ runs
count: 3
var count = 0;
for (var i = 0; i < 3; i++)
{
count++;
}
Console.WriteLine($"count: {count}");
$ dotnet run✓ runs
count: 3
#include <stdio.h>
int main(void) {
int count = 0;
for (int i = 0; i < 3; i++) {
count++;
}
printf("count: %d\n", count);
return 0;
}
$ gcc -Wall -Wextra main.c -o main && ./main✓ runs
count: 3
#include <iostream>
int main() {
int count = 0;
for (int i = 0; i < 3; i++) {
count++;
}
std::cout << "count: " << count << "\n";
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main✓ runs
count: 3
What does the compiler say? Guess first, then open.
$ cargo build
error: Rust has no postfix increment operator
 --> src/main.rs:4:14
  |
4 |         count++;
  |              ^^ not a valid postfix operator
  |
help: use `+= 1` instead
  |
4 -         count++;
4 +         count += 1;
What happens in Go

Go prints 3. count++ is a statement, not an expression, so x := count++ is an error in Go. ++count does not exist.

What happens in Kotlin

Kotlin prints 3. count++ and ++count both work, and can sit inside a larger expression.

What happens in Python

Python refuses with a SyntaxError: it has no ++ operator, so write count += 1. It is the one language here with no ++. Note that ++count and --count are legal in Python and do nothing: they read as +(+count) and -(-count).

What happens in TypeScript

TypeScript prints 3. count++ and ++count work, as in JavaScript, and --count decrements. In Rust --count compiles and negates twice.

What happens in Java

Java prints 3. count++ and ++count work, also inside expressions such as a[i++].

What happens in C#

C# prints 3. count++ and ++count work, also inside expressions.

What happens in C

C prints 3. count++ works, also inside expressions such as a[i++].

What happens in C++

C++ prints 3. count++ and ++count work, and they can be overloaded for your own types.

What Rust sees

Rust has no ++ or -- operator. count++ is not a statement the parser knows, and the compiler stops with a message that says so and shows count += 1.

Why Rust says no

The compiler recognises the habit and names it: "Rust has no postfix increment operator". The fix is count += 1. Python is the other language here with no ++ operator. Elsewhere ++ is part of the language: Go allows it only as a statement, and the others also allow it inside larger expressions.

The fix

Compiles and runs.

Rust, fixedsrc/main.rsRun ↗
fn main() {
let mut count = 0;
for _ in 0..3 {
count += 1;
}
println!("count: {count}");
}
$ cargo run
count: 3

The toolkit

This page lists habits that fail in Rust, with the real compiler message and what to write instead. A message with no error code means the parser did not know the syntax; a code such as E0308 means the syntax was fine and the types were not. Outputs come from Linux on 64-bit ARM with Rust 1.99.1.

Two warnings. When the parser gets lost it names the token where it gave up, and the cause is that token or the one before. And a help: line is a guess: count += 1 is the fix, but goto done gets “write goto.done” and while item = items.next() gets “compare with ==”. Read a guess before you apply it.

Three words recur: Option is a value that may be missing (Option), Result is a success or an error (Errors), and match is Rust’s switch (Match).

Your habits, in Rust

Your own list comes first; the shared list is for everyone.

The shared list

You try Rust way Error Learn more
x++, x-- x += 1; syntax error Operators and loops
c ? a : b if c { a } else { b } syntax error Statements and expressions
null, nil, undefined Option<T> E0425, E0308 Option
i32 with i64, n * 1.5 as, From, try_from E0308, E0277 Numbers and types
if x on text, a list, a number an explicit test E0308 Statements and expressions
break in a switch arm in a match inside a loop it ends the loop compiles Match
throw, catch Result, ? syntax error Errors
f(width = 3), default values a struct, an Option parameter E0425, E0061 Methods
two functions, one name two names, or a trait E0428 Methods
class, extends, new, this struct, a trait, self syntax error, E0423, E0424, E0425 Methods
s[0] s.chars(), s.as_bytes() E0277 Strings
a global assigned anywhere an atomic or a Mutex E0594, E0133 Shared state

Operators and loops

Four habits, one function each so that one run shows every message:

Rustsrc/main.rs✗ won't compile
fn increment() {
let mut count = 0;
count++;
}
fn prefix() {
let mut count = 0;
++count;
}
fn decrement() {
let mut count = 10;
count--;
}
fn ternary(n: i32) -> &'static str {
n % 2 == 0 ? "even" : "odd"
}
fn main() {}
$ cargo build
error: Rust has no postfix increment operator
 --> src/main.rs:3:10
  |
3 |     count++;
  |          ^^ not a valid postfix operator
  |
help: use `+= 1` instead
  |
3 -     count++;
3 +     count += 1;

error: Rust has no prefix increment operator
 --> src/main.rs:8:5
  |
8 |     ++count;
  |     ^^ not a valid prefix operator
  |
help: use `+= 1` instead
  |
8 -     ++count;
8 +     count += 1;

error: Rust has no postfix decrement operator
  --> src/main.rs:13:10
   |
13 |     count--;
   |          ^^ not a valid postfix operator
   |
help: use `-= 1` instead
   |
13 -     count--;
13 +     count -= 1;

error: Rust has no ternary operator
  --> src/main.rs:17:5
   |
17 |     n % 2 == 0 ? "even" : "odd"
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
help: use an `if-else` expression instead
   |
17 -     n % 2 == 0 ? "even" : "odd"
17 +     if n % 2 == 0 { "even" } else { "odd" }

Each message says what to write. a[i++] has no one-token form: write the increment as its own statement, or as a block that ends in the value, { let v = a[i]; i += 1; v }.

--x is not a decrement

++x is a syntax error, like x++. --x is valid, and its meaning depends on the language. In Rust it is -(-x), as in Python, so count is unchanged. In C, C++, Java, C#, Kotlin and TypeScript it decrements, and Go rejects it. A lint (a check for suspicious code) named double_negations, on by default, warns about it:

Rustsrc/main.rsRun ↗
#![allow(double_negations)]
fn main() {
let count = 5;
let after = --count;
println!("count: {count}");
println!("--count: {after}");
}
$ cargo run
count: 5
--count: 5

On an unsigned number it is an error instead (E0600), because an unsigned value cannot be negated:

Rustsrc/main.rs✗ won't compile
fn main() {
let count = 5u32;
let after = --count;
println!("{after}");
}
$ cargo buildWhat is E0600?
error[E0600]: cannot apply unary operator `-` to type `u32`
 --> src/main.rs:3:18
  |
3 |     let after = --count;
  |                  ^^^^^^ cannot apply unary operator `-`
  |
  = note: unsigned values cannot be negated

error[E0600]: cannot apply unary operator `-` to type `u32`
 --> src/main.rs:3:17
  |
3 |     let after = --count;
  |                 ^^^^^^^ cannot apply unary operator `-`
  |
  = note: unsigned values cannot be negated

No null

A value that may be missing has the type Option<T> (Option). String has no missing state, and None belongs to Option, so it does not fit where a String is declared:

Rustsrc/main.rs✗ won't compile
fn main() {
let a = null;
let b: String = None;
let c: Option<i32> = 5;
println!("{a} {b} {c:?}");
}
$ cargo buildWhat is E0425?
error[E0425]: cannot find value `null` in this scope
 --> src/main.rs:2:13
  |
2 |     let a = null;
  |             ^^^^ not found in this scope
  |
help: consider importing this function
  |
1 + use std::ptr::null;

error[E0308]: mismatched types
 --> src/main.rs:3:21
  |
3 |     let b: String = None;
  |            ------   ^^^^ expected `String`, found `Option<_>`
  |            |
  |            expected due to this
  |
  = note: expected struct `String`
               found enum `Option<_>`

error[E0308]: mismatched types
 --> src/main.rs:4:26
  |
4 |     let c: Option<i32> = 5;
  |            -----------   ^ expected `Option<i32>`, found integer
  |            |
  |            expected due to this
  |
  = note: expected enum `Option<i32>`
             found type `{integer}`
help: try wrapping the expression in `Some`
  |
4 |     let c: Option<i32> = Some(5);
  |                          +++++ +

Some errors have detailed explanations: E0308, E0425.

The first message carries a trap. std::ptr::null makes a raw pointer, which the Reference describes as “without safety or liveness guarantees”. Following the hint does not give you an Option; it gives a different error:

Rustsrc/main.rs✗ won't compile
use std::ptr::null;
fn main() {
let a = null;
println!("{a}");
}
$ cargo buildWhat is E0277?
error[E0277]: `fn() -> *const _ {null::<_>}` doesn't implement `std::fmt::Display`
 --> src/main.rs:5:15
  |
5 |     println!("{a}");
  |               ^^^ `fn() -> *const _ {null::<_>}` cannot be formatted with the default formatter
  |
  = help: the trait `std::fmt::Display` is not implemented for fn item `fn() -> *const _ {null::<_>}`
  = note: in format strings you may be able to use `{:?}` (or `{:#?}` for pretty-print) instead

Raw pointers exist for talking to other languages, and dereferencing one is an unsafe operation. In ordinary code, do not follow that hint.

Numbers do not convert themselves

Rust does not turn one number type into another for you, not even a safe widening such as i32 to i64, or an integer into a float. A comparison (small == other) is E0308 too:

Rustsrc/main.rs✗ won't compile
fn main() {
let small: i32 = 5;
let big: u64 = small;
let other: i64 = 5;
println!("{big} {}", small == other);
}
$ cargo buildWhat is E0308?
error[E0308]: mismatched types
 --> src/main.rs:3:20
  |
3 |     let big: u64 = small;
  |              ---   ^^^^^ expected `u64`, found `i32`
  |              |
  |              expected due to this
  |
help: you can convert an `i32` to a `u64` and panic if the converted value doesn't fit
  |
3 |     let big: u64 = small.try_into().unwrap();
  |                         ++++++++++++++++++++

error[E0308]: mismatched types
 --> src/main.rs:5:35
  |
5 |     println!("{big} {}", small == other);
  |                          -----    ^^^^^ expected `i32`, found `i64`
  |                          |
  |                          expected because this is `i32`
  |
help: you can convert `small` from `i32` to `i64`, matching the type of `other`
  |
5 |     println!("{big} {}", i64::from(small) == other);
  |                          ++++++++++     +
Rustsrc/main.rs✗ won't compile
fn main() {
let n = 3;
let scaled = n * 1.5;
println!("{scaled}");
}
$ cargo buildWhat is E0277?
error[E0277]: cannot multiply `{integer}` by `{float}`
 --> src/main.rs:3:20
  |
3 |     let scaled = n * 1.5;
  |                    ^ no implementation for `{integer} * {float}`
  |
  = help: the trait `Mul<{float}>` is not implemented for `{integer}`
  = help: the following other types implement trait `Mul<Rhs>`:
            `&f128` implements `Mul<f128>`
            `&f128` implements `Mul`
            `&f16` implements `Mul<f16>`
            `&f16` implements `Mul`
            `&f32` implements `Mul<f32>`
            `&f32` implements `Mul`
            `&f64` implements `Mul<f64>`
            `&f64` implements `Mul`
          and 57 others

The help shows two fixes: i64::from(small) when no value can be lost, and try_into() when it might not fit. as is the third, and it cuts or rounds without a check (Declarations and types). For the float: f64::from(n) * 1.5.

No fall-through, and break in a match

A match arm runs on its own, so there is nothing to fall through to; several values share an arm with |. A break in an arm leaves the enclosing loop, as 2 => break does here:

Rustsrc/main.rsRun ↗
fn main() {
let mut n = 0;
for i in 0..5 {
match i {
2 => break,
_ => n += 1,
}
}
println!("n: {n}");
}
$ cargo run
n: 2

In Go, Java, C#, C, C++ and TypeScript the same program with a switch leaves only the switch and counts to 4 (your own list above runs it). Rust and Kotlin agree on 2. Neither warns.

No exceptions

throw and catch are not Rust syntax, so the parser stops:

Rustsrc/main.rs✗ won't compile
fn throws(n: i32) -> i32 {
if n < 0 {
throw "negative";
}
n
}
fn catches() {
try {
println!("work");
} catch (e) {
println!("failed");
}
}
fn main() {}
$ cargo build
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `"negative"`
 --> src/main.rs:3:15
  |
3 |         throw "negative";
  |               ^^^^^^^^^^ expected one of 8 possible tokens

error: keyword `catch` cannot follow a `try` block
  --> src/main.rs:11:7
   |
11 |     } catch (e) {
   |       ^^^^^
   |
   = help: try using `match` on the result of the `try` block instead

try is reserved for an experimental feature: the parser accepts a try { } block and stable refuses it (E0658). It is not an exception handler:

Rustsrc/main.rs✗ won't compile
fn main() {
try {
println!("work");
}
}
$ cargo buildWhat is E0658?
error[E0658]: `try` expression is experimental
 --> src/main.rs:2:5
  |
2 | /     try {
3 | |         println!("work");
4 | |     }
  | |_____^
  |
  = note: see issue #154391 <https://github.com/rust-lang/rust/issues/154391> for more information

error[E0277]: a `try` block must return `Result` or `Option` (or another type that implements `Try`)
 --> src/main.rs:4:5
  |
4 |     }
  |     ^ could not wrap the final value of the block as `()` doesn't implement `Try`
  |
  = help: the nightly-only, unstable trait `Try` is not implemented for `()`

Some errors have detailed explanations: E0277, E0658.

A function that can fail returns a Result, and ? passes the failure up to the caller (Errors). A bug is a panic!, which ends the thread. std::panic::catch_unwind exists, but its documentation says “It is not recommended to use this function for a general try/catch mechanism.”

Rustsrc/main.rsRun ↗
use std::num::ParseIntError;
fn double(text: &str) -> Result<i32, ParseIntError> {
let n = text.parse::<i32>()?;
Ok(n * 2)
}
fn main() {
println!("double 21: {:?}", double("21"));
println!("double x: {:?}", double("x"));
}
$ cargo run
double 21: Ok(42)
double x: Err(ParseIntError { kind: InvalidDigit })

No default or named arguments, no function overloading

width = 3 inside a call is an assignment, so the compiler looks for a variable called width; the message does not say “named arguments”. A default value in the parameter list is recognised and refused:

Rustsrc/main.rs✗ won't compile
fn area(width: u32, height: u32) -> u32 {
width * height
}
fn main() {
println!("{}", area(width = 3, height = 4));
println!("{}", area(3));
}
$ cargo buildWhat is E0425?
error[E0425]: cannot find value `width` in this scope
 --> src/main.rs:6:25
  |
6 |     println!("{}", area(width = 3, height = 4));
  |                         ^^^^^ not found in this scope

error[E0425]: cannot find value `height` in this scope
 --> src/main.rs:6:36
  |
6 |     println!("{}", area(width = 3, height = 4));
  |                                    ^^^^^^ not found in this scope

error[E0061]: this function takes 2 arguments but 1 argument was supplied
 --> src/main.rs:7:20
  |
7 |     println!("{}", area(3));
  |                    ^^^^--- argument #2 of type `u32` is missing
  |
note: function defined here
 --> src/main.rs:1:4
  |
1 | fn area(width: u32, height: u32) -> u32 {
  |    ^^^^             -----------
help: provide the argument
  |
7 |     println!("{}", area(3, /* u32 */));
  |                          +++++++++++

Some errors have detailed explanations: E0061, E0425.
Rustsrc/main.rs✗ won't compile
fn area(width: u32, height: u32 = 1) -> u32 {
width * height
}
fn main() {
println!("{}", area(3, 1));
}
$ cargo build
error: parameter defaults are not supported
 --> src/main.rs:1:33
  |
1 | fn area(width: u32, height: u32 = 1) -> u32 {
  |                                 ^^^

Two functions with one name are an error, and Methods shows the version for methods:

Rustsrc/main.rs✗ won't compile
fn show(n: i32) -> String {
format!("int {n}")
}
fn show(s: &str) -> String {
format!("text {s}")
}
fn main() {
println!("{}", show(1));
println!("{}", show("a"));
}
$ cargo buildWhat is E0428?
error[E0428]: the name `show` is defined multiple times
 --> src/main.rs:5:1
  |
1 | fn show(n: i32) -> String {
  | ------------------------- previous definition of the value `show` here
...
5 | fn show(s: &str) -> String {
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^ `show` redefined here
  |
  = note: `show` must be defined only once in the value namespace of this module

error[E0308]: mismatched types
  --> src/main.rs:11:25
   |
11 |     println!("{}", show("a"));
   |                    ---- ^^^ expected `i32`, found `&str`
   |                    |
   |                    arguments to this function are incorrect
   |
note: function defined here
  --> src/main.rs:1:4
   |
 1 | fn show(n: i32) -> String {
   |    ^^^^ ------

Some errors have detailed explanations: E0308, E0428.

The Rust forms are a struct with #[derive(Default)] (fields start as 0, false or empty) and a literal that ends in ..Default::default(), which fills every field not written; or an Option parameter, where None means “use the default”:

Rustsrc/main.rsRun ↗
#[derive(Default)]
struct Window {
width: u32,
height: u32,
border: bool,
}
fn describe(w: &Window) -> String {
format!("{}x{} border={}", w.width, w.height, w.border)
}
fn scaled(width: u32, factor: Option<u32>) -> u32 {
width * factor.unwrap_or(1)
}
fn main() {
let sized = Window {
width: 80,
height: 24,
..Default::default()
};
let bordered = Window {
border: true,
..Default::default()
};
println!("sized: {}", describe(&sized));
println!("bordered: {}", describe(&bordered));
println!("scaled: {} {}", scaled(3, None), scaled(3, Some(4)));
}
$ cargo run
sized: 80x24 border=false
bordered: 0x0 border=true
scaled: 3 12

This is more code than height=1; a type with many settings usually gets a builder (Methods). “No overloading” is narrow: a trait can be implemented for several types and called the same way, and impl Add overloads +.

Indexing text

s[0] does not compile for a string, and the note names the way out: s.chars().next() is an Option<char>, s.as_bytes()[0] a byte:

Rustsrc/main.rs✗ won't compile
fn main() {
let word = String::from("hello");
let first = word[0];
println!("{first}");
}
$ cargo buildWhat is E0277?
error[E0277]: the type `str` cannot be indexed by `{integer}`
 --> src/main.rs:3:22
  |
3 |     let first = word[0];
  |                      ^ string indices are ranges of `usize`
  |
  = help: the trait `SliceIndex<str>` is not implemented for `{integer}`
  = note: you can use `.chars().nth()` or `.bytes().nth()`
          for more information, see chapter 8 in The Book: <https://doc.rust-lang.org/book/ch08-02-strings.html#indexing-into-strings>
  ... (notes pointing into the standard library trimmed)

No mutable globals

A static is a global with one fixed place in memory. A plain one cannot be assigned. static mut can, but every read or write needs an unsafe block (a block where you promise the compiler you checked what it cannot):

Rustsrc/main.rs✗ won't compile
static VISITS: u32 = 0;
fn main() {
VISITS += 1;
println!("{VISITS}");
}
$ cargo buildWhat is E0594?
error[E0594]: cannot assign to immutable static item `VISITS`
 --> src/main.rs:4:5
  |
1 | static VISITS: u32 = 0;
  | ------------------ this `static` cannot be written to
...
4 |     VISITS += 1;
  |     ^^^^^^^^^^^ cannot assign
Rustsrc/main.rs✗ won't compile
static mut VISITS: u32 = 0;
fn main() {
VISITS += 1;
}
$ cargo buildWhat is E0133?
error[E0133]: use of mutable static is unsafe and requires unsafe block
 --> src/main.rs:4:5
  |
4 |     VISITS += 1;
  |     ^^^^^^ use of mutable static
  |
  = note: mutable statics can be mutated by multiple threads: aliasing violations or data races will cause undefined behavior

Inside unsafe it compiles, but edition 2024 denies even printing the value:

Rustsrc/main.rs✗ won't compile
static mut VISITS: u32 = 0;
fn main() {
unsafe {
VISITS += 1;
println!("{}", VISITS);
}
}
$ cargo build
error: creating a shared reference to mutable static
 --> src/main.rs:6:24
  |
6 |         println!("{}", VISITS);
  |                        ^^^^^^ shared reference to mutable static
  |
  = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives
  = help: use a type that relies on "interior mutability" instead; to read more on this, visit <https://doc.rust-lang.org/reference/interior-mutability.html>
  = note: for more information, see <https://doc.rust-lang.org/edition-guide/rust-2024/static-mut-references.html>
  = note: `#[deny(static_mut_refs)]` (part of `#[deny(rust_2024_compatibility)]`) on by default

For a counter that several threads may change, use an atomic: a number whose changes happen as one step. For larger values use a Mutex (Shared state). Every atomic method takes an Ordering, which says how much other memory the operation makes visible to other threads. Ordering::Relaxed keeps the counter itself exact and orders nothing else, which is all a plain counter needs; the Ordering docs explain the stronger ones.

Rustsrc/main.rsRun ↗
use std::sync::atomic::{AtomicU32, Ordering};
static VISITS: AtomicU32 = AtomicU32::new(0);
fn visit() -> u32 {
VISITS.fetch_add(1, Ordering::Relaxed) + 1
}
fn main() {
visit();
visit();
println!("visits: {}", visit());
println!("loaded: {}", VISITS.load(Ordering::Relaxed));
}
$ cargo run
visits: 3
loaded: 3

Going deeper

Where this costs you

  • Numeric code that mixes widths, such as sizes (usize) and counters (i32), has more as and from calls.
  • Without inheritance, shared behaviour comes from a trait, or a type holds the other as a field.

Practice

Try it

Each exercise is a small program with checks in main. Open it in the Playground, replace the todo!(), and run it until it prints all checks passed. Each is labelled with the readers it suits; the first two suit everybody, and the second asks you to read.

A mean with no division by zero (everyone)

Divide an i64 by a u32 after converting both yourself, and return None when there are no readings, so no NaN appears.

Starterexercises/average.rs
// The mean of `count` readings that add up to `total`.
// Rust will not mix an i64 and a u32 in one division: convert both yourself.
// With no readings there is no mean, so return None instead of a NaN.
fn average(total: i64, count: u32) -> Option<f64> {
todo!("rule out count == 0, then divide as floats")
}
fn main() {
assert_eq!(average(7, 2), Some(3.5));
assert_eq!(average(-7, 2), Some(-3.5));
assert_eq!(average(0, 4), Some(0.0));
assert_eq!(average(0, 0), None);
assert_eq!(average(5, 0), None);
println!("all checks passed");
}

Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.

Show a solution
One solutionexercises/average.solution.rsRun ↗
// The mean of `count` readings that add up to `total`.
// Rust will not mix an i64 and a u32 in one division: convert both yourself.
// With no readings there is no mean, so return None instead of a NaN.
fn average(total: i64, count: u32) -> Option<f64> {
if count == 0 {
return None;
}
Some(total as f64 / f64::from(count))
}
fn main() {
assert_eq!(average(7, 2), Some(3.5));
assert_eq!(average(-7, 2), Some(-3.5));
assert_eq!(average(0, 4), Some(0.0));
assert_eq!(average(0, 0), None);
assert_eq!(average(5, 0), None);
println!("all checks passed");
}
$ cargo run
all checks passed

Read this: what does it give? (everyone)

Reading, not writing: say what a loop with a break inside a match, -7 / 2, and a ? give, for None, an empty string and a real one. The checks run the real code and name the item you misread, not the right answer.

Starterexercises/read-habits.rs
// Read this program. Replace each `todo!()` with what the marked call gives, then run
// until the checks print `all checks passed`.
fn tally() -> i32 {
let mut total = 0;
for i in 0..6 {
match i {
1 | 2 => total += 10,
3 => break,
_ => total += 1,
}
}
total
}
fn halves() -> (i32, i32) {
(-7 / 2, 7 / 2)
}
fn city_len(city: Option<&str>) -> Option<usize> {
let c = city?;
Some(c.len())
}
/// Hint: how many times does the loop body run, and what does `break` leave?
fn tally_is() -> i32 {
todo!("how many times does the loop body run, and what does `break` leave?")
}
/// Hint: the value of `halves()`: integer division with a negative number
fn halves_are() -> (i32, i32) {
todo!("the value of `halves()`: integer division with a negative number")
}
/// Hint: what does `city_len(None)` give? Look at what `?` does
fn no_city_gives() -> Option<usize> {
todo!("what does `city_len(None)` give? Look at what `?` does")
}
/// Hint: what does `city_len(Some(""))` give? Is an empty string a missing value?
fn empty_city_gives() -> Option<usize> {
todo!("what does `city_len(Some(\"\"))` give? Is an empty string a missing value?")
}
/// Hint: what does `city_len(Some("Oslo"))` give?
fn oslo_gives() -> Option<usize> {
todo!("what does `city_len(Some(\"Oslo\"))` give?")
}
fn check<T: PartialEq + std::fmt::Debug>(call: &str, said: T, truth: T) {
assert!(said == truth, "misread `{call}`: you said {said:?}");
println!("ok: {call} = {said:?}");
}
fn main() {
check("tally()", tally_is(), tally());
check("halves()", halves_are(), halves());
check("city_len(None)", no_city_gives(), city_len(None));
check(
"city_len(Some(\"\"))",
empty_city_gives(),
city_len(Some("")),
);
check(
"city_len(Some(\"Oslo\"))",
oslo_gives(),
city_len(Some("Oslo")),
);
println!("all checks passed");
}

Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.

Show a solution
One solutionexercises/read-habits.solution.rsRun ↗
// Read this program. Replace each `todo!()` with what the marked call gives, then run
// until the checks print `all checks passed`.
fn tally() -> i32 {
let mut total = 0;
for i in 0..6 {
match i {
1 | 2 => total += 10,
3 => break,
_ => total += 1,
}
}
total
}
fn halves() -> (i32, i32) {
(-7 / 2, 7 / 2)
}
fn city_len(city: Option<&str>) -> Option<usize> {
let c = city?;
Some(c.len())
}
/// Hint: how many times does the loop body run, and what does `break` leave?
fn tally_is() -> i32 {
21
}
/// Hint: the value of `halves()`: integer division with a negative number
fn halves_are() -> (i32, i32) {
(-3, 3)
}
/// Hint: what does `city_len(None)` give? Look at what `?` does
fn no_city_gives() -> Option<usize> {
None
}
/// Hint: what does `city_len(Some(""))` give? Is an empty string a missing value?
fn empty_city_gives() -> Option<usize> {
Some(0)
}
/// Hint: what does `city_len(Some("Oslo"))` give?
fn oslo_gives() -> Option<usize> {
Some(4)
}
fn check<T: PartialEq + std::fmt::Debug>(call: &str, said: T, truth: T) {
assert!(said == truth, "misread `{call}`: you said {said:?}");
println!("ok: {call} = {said:?}");
}
fn main() {
check("tally()", tally_is(), tally());
check("halves()", halves_are(), halves());
check("city_len(None)", no_city_gives(), city_len(None));
check(
"city_len(Some(\"\"))",
empty_city_gives(),
city_len(Some("")),
);
check(
"city_len(Some(\"Oslo\"))",
oslo_gives(),
city_len(Some("Oslo")),
);
println!("all checks passed");
}
$ cargo run
ok: tally() = 21
ok: halves() = (-3, 3)
ok: city_len(None) = None
ok: city_len(Some("")) = Some(0)
ok: city_len(Some("Oslo")) = Some(4)
all checks passed

Read more