Rust
Rust
Contents
Variable bindings
The let keyword
Type annotation
Uninitialized variables
Throwing values away
Shadowing bindings
Tuples
Destructuring tuples
Statements
Functions
Blocks
Blocks are expressions
Implicit return
Everything is an expression
Field access and method calling
Modules, use syntax
Types are namespaces too
The libstd prelude
Structs
Struct update syntax
Destructuring structs
Patterns and destructuring
Destructuring with if let
Match arms are patterns
Exhaustive matches
Methods
Immutability
Traits
Orphan rules
The Self type
Marker traits
Trait method receivers
Deriving traits
Generics
Generic functions
Type parameter constraints (trait bounds)
Monomorphization
Generic structs
Example: Vec
Macros
The panic! macro
Functions that panic
Enums (sum types)
Lifetimes
Borrowing rules (one or more immutable borrows XOR one mutable
borrow)
Functions generic over lifetimes
Lifetime elision
Structs generic over lifetimes
Implementations generic over lifetimes
The 'static lifetime
Struct literal assignment shorthand
Owned types vs reference types
Slices
Operator overloading
Borrowing rules and slices
String slices ( &str )
Fallible functions ( Result )
Dereferencing
Function types, closures
FnMut and borrowing rules
The toilet closure
Loops, iterators
Returning closures
Capturing into a closure
Conclusion
But how can you read a lot of it if you don't know what it means?
In this article, instead of focusing on one or two concepts, I'll try to go through
as many Rust snippets as I can, and explain what the keywords and symbols
they contain mean.
Ready? Go!
Variable bindings
The let keyword
Rust code
Rust code
let x = 42;
Type annotation
You can specify the variable's type explicitly with : , that's a type annotation:
Rust code
Rust code
Uninitialized variables
If you declare a name and initialize it later, the compiler will prevent you from
using it before it's initialized.
Rust code
let x;
foobar(x); // error: borrow of possibly-uninitialized variable: `x`
x = 42;
Rust code
let x;
x = 42;
foobar(x); // the type of `x` will be inferred from here
Rust code
Names that start with an underscore are regular names, it's just that the
compiler won't warn about them being unused:
Rust code
Separate bindings with the same name can be introduced - you can shadow a
variable binding:
Rust code
let x = 13;
let x = x + 3;
// using `x` after that line only refers to the second `x`,
//
// although the first `x` still exists (it'll be dropped
// when going out of scope), you can no longer refer to it.
Tuples
Rust has tuples, which you can think of as "fixed-length collections of values of
different types".
Rust code
Rust code
Destructuring tuples
Rust code
Of course, when destructuring a tuple, _ can be used to throw away part of it:
Rust code
Statements
Rust code
let x = 3;
let y = 5;
let z = y + x;
Rust code
let x = vec![1, 2, 3, 4, 5, 6, 7, 8]
.iter()
.map(|x| x + 3)
.fold(0, |x, y| x + y);
fn declares a function.
Rust code
fn greet() {
println!("Hi there!");
}
And here's a function that returns a 32-bit signed integer. The arrow indicates
its return type:
Rust code
Blocks
Rust code
Rust code
// this:
let x = 42;
// is equivalent to this:
let x = { 42 };
Rust code
let x = {
let y = 1; // first statement
let z = 2; // second statement
y + z // this is the *tail* - what the whole block will evaluate to
};
Implicit return
And that's why "omitting the semicolon at the end of a function" is the same as
returning, ie. these are equivalent:
Rust code
Everything is an expression
if conditionals are also expressions:
Rust code
Rust code
Rust code
In this example, std is a crate (~ a library), cmp is a module (~ a source file), and
min is a function:
Rust code
use directives can be used to "bring in scope" names from other namespace:
Rust code
use std::cmp::min;
Within use directives, curly brackets have another meaning: they're "globs". If
we want to import both min and max , we can do any of these:
Rust code
// this works:
use std::cmp::min;
use std::cmp::max;
Rust code
// this brings `min` and `max` in scope, and many other things
use std::cmp::*;
Types are namespaces too, and methods can be called as regular functions:
Rust code
str is a primitive type, but many non-primitive types are also in scope by
default.
Rust code
// this is exactly the same code, but with the *full* path to `Vec`
let v = std::vec::Vec::new();
This works because Rust inserts this at the beginning of every module:
Rust code
use std::prelude::v1::*;
(Which in turns re-exports a lot of symbols, like Vec , String , Option and
Result ).
Structs
Rust code
struct Vec2 {
x: f64, // 64-bit floating point, aka "double precision"
y: f64,
}
Rust code
There is a shortcut for initializing the rest of the fields from another struct:
Rust code
let v3 = Vec2 {
x: 14.0,
..v2
};
This is called "struct update syntax", can only happen in last position, and
cannot be followed by a comma.
Note that the rest of the fields can mean all the fields:
Rust code
Destructuring structs
Rust code
So is this:
Rust code
And this:
Rust code
let Vec2 { x, .. } = v;
// this throws away `v.y`
struct Number {
odd: bool,
value: i32,
}
fn main() {
let one = Number { odd: true, value: 1 };
let two = Number { odd: false, value: 2 };
print_number(one);
print_number(two);
}
fn print_number(n: Number) {
if let Number { odd: true, value } = n {
println!("Odd number: {}", value);
} else if let Number { odd: false, value } = n {
println!("Even number: {}", value);
}
}
// this prints:
// Odd number: 1
// Even number: 2
Rust code
fn print_number(n: Number) {
match n {
Number { odd: true, value } => println!("Odd number: {}", value),
Number { odd: false, value } => println!("Even number: {}", value),
}
}
Exhaustive matches
fn print_number(n: Number) {
match n {
Number { value: 1, .. } => println!("One"),
Number { value: 2, .. } => println!("Two"),
Number { value, .. } => println!("{}", value),
// if that last arm didn't exist, we would get a compile-time error
}
}
Rust code
fn print_number(n: Number) {
match n.value {
1 => println!("One"),
2 => println!("Two"),
_ => println!("{}", n.value),
}
}
Methods
Rust code
struct Number {
odd: bool,
value: i32,
}
impl Number {
fn is_strictly_positive(self) -> bool {
self.value > 0
}
}
fn main() {
let minus_two = Number {
odd: false,
value: -2,
};
println!("positive? {}", minus_two.is_strictly_positive());
// this prints "positive? false"
}
Immutability
Variable bindings are immutable by default, which means their interior can't
be mutated:
Rust code
fn main() {
let n = Number {
odd: true,
value: 17,
};
n.odd = false; // error: cannot assign to `n.odd`,
// as `n` is not declared to be mutable
}
Rust code
fn main() {
let n = Number {
odd: true,
value: 17,
};
n = Number {
odd: false,
value: 22,
}; // error: cannot assign twice to immutable variable `n`
}
mut makes a variable binding mutable:
Rust code
fn main() {
let mut n = Number {
odd: true,
value: 17,
}
n.value = 19; // all good
}
Traits
Rust code
trait Signed {
fn is_strictly_negative(self) -> bool;
}
Orphan rules
fn main() {
let n = Number { odd: false, value: -44 };
println!("{}", n.is_strictly_negative()); // prints "true"
}
Rust code
fn main() {
let n: i32 = -44;
println!("{}", n.is_strictly_negative()); // prints "true"
}
fn main() {
let n = Number { odd: true, value: 987 };
let m = -n; // this is only possible because we implemented `Neg`
println!("{}", m.value); // prints "-987"
}
An impl block is always for a type, so, inside that block, Self means that type:
Rust code
Marker traits
Some traits are markers - they don't say that a type implements some methods,
they say that certain things can be done with a type.
For example, i32 implements trait Copy (in short, i32 is Copy ), so this works:
Rust code
fn main() {
let a: i32 = 15;
let b = a; // `a` is copied
let c = a; // `a` is copied again
}
Rust code
fn print_i32(x: i32) {
println!("x = {}", x);
}
fn main() {
let a: i32 = 15;
print_i32(a); // `a` is copied
print_i32(a); // `a` is copied again
}
Rust code
fn main() {
let n = Number { odd: true, value: 51 };
let m = n; // `n` is moved into `m`
let o = n; // error: use of moved value: `n`
}
fn print_number(n: Number) {
println!("{} number {}", if n.odd { "odd" } else { "even" }, n.value);
}
fn main() {
let n = Number { odd: true, value: 51 };
print_number(n); // `n` is moved
print_number(n); // error: use of moved value: `n`
}
Rust code
fn print_number(n: &Number) {
println!("{} number {}", if n.odd { "odd" } else { "even" }, n.value);
}
fn main() {
let n = Number { odd: true, value: 51 };
print_number(&n); // `n` is borrowed for the time of the call
print_number(&n); // `n` is borrowed again
}
It also works if a function takes a mutable reference - but only if our variable
binding is also mut .
Rust code
fn print_number(n: &Number) {
println!("{} number {}", if n.odd { "odd" } else { "even" }, n.value);
}
fn main() {
// this time, `n` is mutable
let mut n = Number { odd: true, value: 51 };
print_number(&n);
invert(&mut n); // `n is borrowed mutably - everything is explicit
print_number(&n);
}
Rust code
Rust code
fn main() {
let n = Number { odd: true, value: 51 };
let mut m = n.clone();
m.value += 100;
print_number(&n);
print_number(&m);
}
To highlight this: these are equivalent:
Rust code
let m = n.clone();
let m = std::clone::Clone::clone(&n);
Rust code
Rust code
fn main() {
let n = Number { odd: true, value: 51 };
let m = n.clone();
let o = n.clone();
}
Rust code
fn main() {
let n = Number { odd: true, value: 51 };
let m = n; // `m` is a copy of `n`
let o = n; // same. `n` is neither moved nor borrowed.
}
Deriving traits
Rust code
#[derive(Clone, Copy)]
struct Number {
odd: bool,
value: i32,
}
// this expands to `impl Clone for Number` and `impl Copy for Number` blocks.
Generics
Generic functions
Rust code
fn foobar<T>(arg: T) {
// do something with `arg`
}
They can have multiple type parameters, which can then be used in the
function's declaration and its body, instead of concrete types:
Rust code
Rust code
fn print<T: Display>(value: T) {
println!("value = {}", value);
}
fn print<T: Debug>(value: T) {
println!("value = {:?}", value);
}
Rust code
fn print<T>(value: T)
where
T: Display,
{
println!("value = {}", value);
}
use std::fmt::Debug;
fn compare<T>(left: T, right: T)
where
T: Debug + PartialEq,
{
println!("{:?} {} {:?}", left, if left == right { "==" } else { "!=" }, right);
}
fn main() {
compare("tea", "coffee");
// prints: "tea" != "coffee"
}
Monomorphization
Same as with crates, and modules, and types, generic functions can be
"explored" (navigated?) using ::
Rust code
fn main() {
use std::any::type_name;
println!("{}", type_name::<i32>()); // prints "i32"
println!("{}", type_name::<(f64, char)>()); // prints "(f64, char)"
}
This is lovingly called turbofish syntax, because ::<> looks like a fish.
Generic structs
struct Pair<T> {
a: T,
b: T,
}
fn print_type_name<T>(_val: &T) {
println!("{}", std::any::type_name::<T>());
}
fn main() {
let p1 = Pair { a: 3, b: 9 };
let p2 = Pair { a: true, b: false };
print_type_name(&p1); // prints "Pair<i32>"
print_type_name(&p2); // prints "Pair<bool>"
}
Example: Vec
Rust code
fn main() {
let mut v1 = Vec::new();
v1.push(1);
let mut v2 = Vec::new();
v2.push(false);
print_type_name(&v1); // prints "Vec<i32>"
print_type_name(&v2); // prints "Vec<bool>"
}
Speaking of Vec , it comes with a macro that gives more or less "vec literals":
Rust code
fn main() {
let v1 = vec![1, 2, 3];
let v2 = vec![true, false, true];
print_type_name(&v1); // prints "Vec<i32>"
print_type_name(&v2); // prints "Vec<bool>"
}
Macros
Rust code
fn main() {
println!("{}", "Hello there!");
}
Rust code
fn main() {
use std::io::{self, Write};
io::stdout().lock().write_all(b"Hello there!\n").unwrap();
}
panic is also a macro. It violently stops execution with an error message, and
the file name / line number of the error, if enabled:
Rust code
fn main() {
panic!("This panics");
}
// output: thread 'main' panicked at 'This panics', src/main.rs:3:5
Rust code
fn main() {
let o1: Option<i32> = Some(128);
o1.unwrap(); // this is fine
enum Option<T> {
None,
Some(T),
}
impl<T> Option<T> {
fn unwrap(self) -> T {
// enums variants can be used in patterns:
match self {
Self::Some(t) => t,
Self::None => panic!(".unwrap() called on a None option"),
}
}
}
fn main() {
let o1: Option<i32> = Some(128);
o1.unwrap(); // this is fine
Rust code
Lifetimes
fn main() {
// `x` doesn't exist yet
{
let x = 42; // `x` starts existing
println!("x = {}", x);
// `x` stops existing
}
// `x` no longer exists
}
Rust code
fn main() {
// `x` doesn't exist yet
{
let x = 42; // `x` starts existing
let x_ref = &x; // `x_ref` starts existing - it borrows `x`
println!("x_ref = {}", x_ref);
// `x_ref` stops existing
// `x` stops existing
}
// `x` no longer exists
}
The lifetime of a reference cannot exceed the lifetime of the variable binding it
borrows:
Rust code
fn main() {
let x_ref = {
let x = 42;
&x
};
println!("x_ref = {}", x_ref);
// error: `x` does not live long enough
}
Rust code
fn main() {
let x = 42;
let x_ref1 = &x;
let x_ref2 = &x;
let x_ref3 = &x;
println!("{} {} {}", x_ref1, x_ref2, x_ref3);
}
Rust code
fn main() {
let mut x = 42;
let x_ref = &x;
x = 13;
println!("x_ref = {}", x_ref);
// error: cannot assign to `x` because it is borrowed
}
Rust code
fn main() {
let mut x = 42;
let x_ref1 = &x;
let x_ref2 = &mut x;
// error: cannot borrow `x` as mutable because it is also borrowed as immutable
println!("x_ref1 = {}", x_ref1);
}
fn print(x: &i32) {
// `x` is borrowed (from the outside) for the
// entire time this function is called.
}
Functions with reference arguments can be called with borrows that have
different lifetimes, so:
Rust code
// named lifetimes:
fn print<'a>(x: &'a i32) {}
This allows returning references whose lifetime depend on the lifetime of the
arguments:
Rust code
struct Number {
value: i32,
}
fn main() {
let n = Number { value: 47 };
let v = number_value(&n);
// `v` borrows `n` (immutably), thus: `v` cannot outlive `n`.
// While `v` exists, `n` cannot be mutably borrowed, mutated, moved, etc.
}
Lifetime elision
Rust code
Structs can also be generic over lifetimes, which allows them to hold references:
Rust code
struct NumRef<'a> {
x: &'a i32,
}
fn main() {
let x: i32 = 99;
let x_ref = NumRef { x: &x };
// `x_ref` cannot outlive `x`, etc.
}
Rust code
struct NumRef<'a> {
x: &'a i32,
}
fn main() {
let x: i32 = 99;
let x_ref = as_num_ref(&x);
// `x_ref` cannot outlive `x`, etc.
}
struct NumRef<'a> {
x: &'a i32,
}
fn main() {
let x: i32 = 99;
let x_ref = as_num_ref(&x);
// `x_ref` cannot outlive `x`, etc.
}
Rust code
impl<'a> NumRef<'a> {
fn as_i32_ref(&'a self) -> &'a i32 {
self.x
}
}
fn main() {
let x: i32 = 99;
let x_num_ref = NumRef { x: &x };
let x_i32_ref = x_num_ref.as_i32_ref();
// neither ref can outlive `x`
}
Rust code
impl<'a> NumRef<'a> {
fn as_i32_ref(&self) -> &i32 {
self.x
}
}
You can elide even harder, if you never need the name:
Rust code
impl NumRef<'_> {
fn as_i32_ref(&self) -> &i32 {
self.x
}
}
There is a special lifetime, named 'static , which is valid for the entire
program's lifetime.
Rust code
struct Person {
name: &'static str,
}
fn main() {
let p = Person {
name: "fasterthanlime",
};
}
struct Person {
name: &'static str,
}
fn main() {
let name = format!("fasterthan{}", "lime");
let p = Person { name: &name };
// error: `name` does not live long enough
}
In that last example, the local name is not a &'static str , it's a String . It's
been allocated dynamically, and it will be freed. Its lifetime is less than the
whole program (even though it happens to be in main ).
Rust code
struct Person<'a> {
name: &'a str,
}
fn main() {
let name = format!("fasterthan{}", "lime");
let p = Person { name: &name };
// `p` cannot outlive `name`
}
or
struct Person {
name: String,
}
fn main() {
let name = format!("fasterthan{}", "lime");
let p = Person { name: name };
// `name` was moved into `p`, their lifetimes are no longer tied.
}
Speaking of: in a struct literal, when a field is set to a variable binding of the
same name:
Rust code
Rust code
Tools like clippy will suggest making those changes, and even apply the fix
programmatically if you let it.
For many types in Rust, there are owned and non-owned variants:
Slices
Rust code
fn main() {
let v = vec![1, 2, 3, 4, 5];
let v2 = &v[2..4];
println!("v2 = {:?}", v2);
}
// output:
// v2 = [3, 4]
Operator overloading
The .. syntax is just range literals. Ranges are just a few structs defined in the
standard library.
They can be open-ended, and their rightmost bound can be inclusive, if it's
preceded by = .
Rust code
fn main() {
// 0 or greater
println!("{:?}", (0..).contains(&100)); // true
// strictly less than 20
println!("{:?}", (..20).contains(&20)); // false
// 20 or less than 20
println!("{:?}", (..=20).contains(&20)); // true
// only 3, 4, 5
println!("{:?}", (3..6).contains(&4)); // true
}
Rust code
fn main() {
let x = &[1, 2, 3, 4, 5];
let y = tail(x);
println!("y = {:?}", y);
}
Rust code
This is legal:
Rust code
fn main() {
let y = {
let x = &[1, 2, 3, 4, 5];
tail(x)
};
println!("y = {:?}", y);
}
Rust code
fn main() {
let y = {
let v = vec![1, 2, 3, 4, 5];
tail(&v)
// error: `v` does not live long enough
};
println!("y = {:?}", y);
}
fn main() {
let name = "Read me. Or don't.txt";
if let Some(ext) = file_ext(name) {
println!("file extension: {}", ext);
} else {
println!("no file extension");
}
}
Rust code
fn main() {
let ext = {
let name = String::from("Read me. Or don't.txt");
file_ext(&name).unwrap_or("")
// error: `name` does not live long enough
};
println!("extension: {:?}", ext);
}
fn main() {
let s = std::str::from_utf8(&[240, 159, 141, 137]);
println!("{:?}", s);
// prints: Ok(" ")
Rust code
fn main() {
let s = std::str::from_utf8(&[240, 159, 141, 137]).unwrap();
println!("{:?}", s);
// prints: " "
Rust code
fn main() {
let s = std::str::from_utf8(&[195, 40]).expect("valid utf-8");
// prints: thread 'main' panicked at 'valid utf-8: Utf8Error
// { valid_up_to: 0, error_len: Some(1) }', src/libcore/result.rs:1165:5
}
fn main() {
match std::str::from_utf8(&[240, 159, 141, 137]) {
Ok(s) => println!("{}", s),
Err(e) => panic!(e),
}
// prints
}
Rust code
fn main() {
if let Ok(s) = std::str::from_utf8(&[240, 159, 141, 137]) {
println!("{}", s);
}
// prints
}
Rust code
Rust code
The * operator can be used to dereference, but you don't need to do that to
access fields or call methods:
Rust code
struct Point {
x: f64,
y: f64,
}
fn main() {
let p = Point { x: 1.0, y: 3.0 };
let p_ref = &p;
println!("({}, {})", p_ref.x, p_ref.y);
}
Rust code
struct Point {
x: f64,
y: f64,
}
fn main() {
let p = Point { x: 1.0, y: 3.0 };
let p_ref = &p;
negate(*p_ref);
// error: cannot move out of `*p_ref` which is behind a shared reference
}
Rust code
fn main() {
let p = Point { x: 1.0, y: 3.0 };
let p_ref = &p;
negate(*p_ref); // ...and now this works
}
Closures are just functions of type Fn , FnMut or FnOnce with some captured
context.
fn for_each_planet<F>(f: F)
where F: Fn(&'static str)
{
f("Earth");
f("Mars");
f("Jupiter");
}
fn main() {
for_each_planet(|planet| println!("Hello, {}", planet));
}
// prints:
// Hello, Earth
// Hello, Mars
// Hello, Jupiter
Rust code
fn for_each_planet<F>(f: F)
where F: Fn(&'static str)
{
f("Earth");
f("Mars");
f("Jupiter");
}
fn main() {
let greeting = String::from("Good to see you");
for_each_planet(|planet| println!("{}, {}", greeting, planet));
// our closure borrows `greeting`, so it cannot outlive it
}
fn for_each_planet<F>(f: F)
where F: Fn(&'static str) + 'static // `F` must now have "'static" lifetime
{
f("Earth");
f("Mars");
f("Jupiter");
}
fn main() {
let greeting = String::from("Good to see you");
for_each_planet(|planet| println!("{}, {}", greeting, planet));
// error: closure may outlive the current function, but it borrows
// `greeting`, which is owned by the current function
}
Rust code
fn main() {
let greeting = String::from("You're doing great");
for_each_planet(move |planet| println!("{}, {}", greeting, planet));
// `greeting` is no longer borrowed, it is *moved* into
// the closure.
}
This is legal:
Rust code
fn foobar<F>(f: F)
where F: Fn(i32) -> i32
{
println!("{}", f(f(2)));
}
fn main() {
foobar(|x| x * 2);
}
// output: 8
This isn't:
Rust code
fn foobar<F>(mut f: F)
where F: FnMut(i32) -> i32
{
println!("{}", f(f(2)));
// error: cannot borrow `f` as mutable more than once at a time
}
fn main() {
foobar(|x| x * 2);
}
Rust code
fn foobar<F>(mut f: F)
where F: FnMut(i32) -> i32
{
let tmp = f(2);
println!("{}", f(tmp));
}
fn main() {
foobar(|x| x * 2);
}
// output: 8
FnMut exists because some closures mutably borrow local variables:
Rust code
fn foobar<F>(mut f: F)
where F: FnMut(i32) -> i32
{
let tmp = f(2);
println!("{}", f(tmp));
}
fn main() {
let mut acc = 2;
foobar(|x| {
acc += 1;
x * acc
});
}
// output: 24
Rust code
fn foobar<F>(f: F)
where F: Fn(i32) -> i32
{
println!("{}", f(f(2)));
}
fn main() {
let mut acc = 2;
foobar(|x| {
acc += 1;
// error: cannot assign to `acc`, as it is a
// captured variable in a `Fn` closure.
// the compiler suggests "changing foobar
// to accept closures that implement `FnMut`"
x * acc
});
}
FnOnce closures can only be called once. They exist because some closure
move out variables that have been moved when captured:
Rust code
fn foobar<F>(f: F)
where F: FnOnce() -> String
{
println!("{}", f());
}
fn main() {
let s = String::from("alright");
foobar(move || s);
// `s` was moved into our closure, and our
// closures moves it to the caller by returning
// it. Remember that `String` is not `Copy`.
}
Rust code
fn foobar<F>(f: F)
where F: FnOnce() -> String
{
println!("{}", f());
println!("{}", f());
// error: use of moved value: `f`
}
And, if you need convincing that our closure does move s , this is illegal too:
Rust code
fn main() {
let s = String::from("alright");
foobar(move || s);
foobar(move || s);
// use of moved value: `s`
}
Rust code
fn main() {
let s = String::from("alright");
foobar(|| s.clone());
foobar(|| s.clone());
}
Rust code
fn main() {
foobar(32, 64, |x, y| x > y);
}
fn main() {
foobar(32, 64, |_, _| panic!("Comparing is futile!"));
}
Rust code
fn main() {
countdown(3, |i| println!("tick {}...", i));
}
// output:
// tick 3...
// tick 2...
// tick 1...
Rust code
fn main() {
countdown(3, |_| ());
}
Loops, iterators
Anything that is iterable can be used in a for in loop.
We've just seen a range being used, but it also works with a Vec :
Rust code
fn main() {
for i in vec![52, 49, 21] {
println!("I like the number {}", i);
}
}
Or a slice:
Rust code
fn main() {
for i in &[52, 49, 21] {
println!("I like the number {}", i);
}
}
// output:
// I like the number 52
// I like the number 49
// I like the number 21
Or an actual iterator:
Rust code
fn main() {
// note: `&str` also has a `.bytes()` iterator.
// Rust's `char` type is a "Unicode scalar value"
for c in "rust".chars() {
println!("Give me a {}", c);
}
}
// output:
// Give me a r
// Give me a u
// Give me a s
// Give me a t
Even if the iterator items are filtered and mapped and flattened:
Rust code
fn main() {
for c in "SuRPRISE INbOUND"
.chars()
.filter(|c| c.is_lowercase())
.flat_map(|c| c.to_uppercase())
{
print!("{}", c);
}
println!();
}
// output: UB
Returning closures
fn main() {
// you can use `.into()` to perform conversions
// between various types, here `&'static str` and `String`
let test = make_tester("hunter2".into());
println!("{}", test("******"));
println!("{}", test("hunter2"));
}
Rust code
fn main() {
let test = make_tester("hunter2");
println!("{}", test("*******"));
println!("{}", test("hunter2"));
}
// output:
// false
// true
Conclusion
And with that, we hit the 30-minute estimated reading time mark, and you
should be able to read most of the Rust code you find online.
Writing Rust is a very different experience from reading Rust. On one hand,
you're not reading the solution to a problem, you're actually solving it. On the
other hand, the Rust compiler helps out a lot.
The Rust compiler has high-quality diagnostics (which include suggestions) for
all the mistakes featured in this article.
And when there's a hint missing, the compiler team is not afraid to add it.
I also blog about Rust and post a lot about Rust on Mastodon and Twitter a lot,
so if you liked this article, you know what to do!
Have fun!
Recommended reading:
Sam Rose Luna Xe Iaso Dan Luu