Rust Note
Installation
Install Rust
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
Update Rust
rustup update
Uninstall Rust
rustup self uninstall
IDE
Vscode
rust-analyzer: A fast, featureful language server for Rust
CodeLLDB: Native debugger for Rust and other languages
Even Better TOML: Better TOML support
Error Lens: Improve highlighting of errors, warnings and other language diagnostics
Cargo
cargo new project-name
: Create a new projectcargo build
: Build the project,--release
for release buildcargo run
: Run the project,--release
for release buildcargo check
: Check the projectCargo.toml: Project configuration file
Cargo.lock: Dependency lock file
Basic Syntax
Variable
variable binding
rustlet x = 5;
mutable variable
rustlet mut x = 5;
never used variable
rustlet _x = 5; // can use _ to ignore the warning
variable destructuring
rustlet (x, y) = (1, 2);
variable shadowing
rustlet x = 5; let x = x + 1;
constant
rustconst MAX_POINTS: u32 = 100_000; // can't use mut and must be type annotated
Data Type
Scalar Type
integer
Length Signed Unsigned 8-bit i8 u8 16-bit i16 u16 32-bit i32 u32 64-bit i64 u64 128-bit i128 u128 arch isize usize integer overflow
rustlet x: u8 = 255; let y = x + 1; // if debug build, panic; if release build, two's complement wrapping
float
Length Type 32-bit f32 64-bit f64 - avoid comparing floats
NaN: to represent the result of an operation that can't return a valid value
rustlet x = 0.0 / 0.0; // NaN, you can use is_nan() to check
Math Operation
addition
rustlet sum = 5 + 10;
subtraction
rustlet difference = 95.5 - 4.3;
multiplication
rustlet product = 4 * 30;
division
rustlet quotient = 56.7 / 32.2;
remainder
rustlet remainder = 43 % 5;
Logical Operation
and
rustlet x = true && false;
or
rustlet x = true || false;
not
rustlet x = !true;
bit
rustlet x = 1 | 2; // 3 let x = 1 & 2; // 0 let x = 1 ^ 2; // 3 let x = 1 << 2; // 4 let x = 4 >> 2; // 1 let x = !1; // -2
Range
rustlet x = 1..=5; // 1, 2, 3, 4, 5 let x = 1..5; // 1, 2, 3, 4
Use
As
to Convert Data Typerustlet x: i32 = 5; let y = x as i64;
Character、Boolean、Unit Type
character
rustlet x = 'z'; let y = '😻'; let z = '草' println!("{}", std::mem::size_of_val(&z)); // 4 // char use `'` and string use `"`
boolean
rustlet x = true; let y: bool = false;
unit type
rustlet x = (); // usually used as placeholder, not consume memory
Statement and Expression
statement
rustlet x = 5; // `let x = 5` is a statement
expression
rustlet y = { let x = 3; x + 1 // `x + 1` is an expression, expression don't include ending semicolon };
Function
function definition
rustfn add(x: i32, y: i32) -> i32 { x + y }
- use snake case for function and variable names
- any position of the function definition is ok
- use
->
to specify the return type - every parameter must have a type annotation
- return value is the last expression in the function body
- use
return
to return early - use
;
to return()
!
is the never type, represents a function that never returns
Ownership
Move and Clone
principle
- each value in Rust has a variable that's called its owner
- there can only be one owner at a time
- when the owner goes out of scope, the value will be dropped
stack and heap
- stack: LIFO, fixed size, fast
- heap: dynamic size, slower
ownership move
rustlet s1 = String::from("hello"); let s2 = s1; // s1 is invalid, rust will directly copy basic data type, but String is not basic data type, so the ownership of s1 is moved to s2
deep copy
rustlet s1 = String::from("hello"); let s2 = s1.clone(); // deep copy, use a lot of resources
shallow copy
any type implement
Copy
trait can use shallow copy. This include all the combination of basic data type and types that do not require memory or resource allocation.ownership and function
rustfn take_ownership(s: String) { println!("{}", s); } fn main() { let s = String::from("hello"); take_ownership(s); // s is invalid }
rustfn make_copy(x: i32) { println!("{}", x); } fn main() { let x = 5; make_copy(x); // x is valid }
ownership and return value
rustfn return_ownership() -> String { let s = String::from("hello"); s } fn main() { let s1 = return_ownership(); // ownership is moved to s1 }
rustfn return_ownership() -> String { let s = String::from("hello"); &s } fn main() { let s1 = return_ownership(); // s1 is invalid, s is dropped }
Reference and Borrowing
reference
rustlet x = 5; let y = &x; // y is a reference to x assert_eq!(5, x); assert_eq!(5, *y); // dereference
immutable reference
rustfn calculate_length(s: &String) -> usize { s.len() } fn main() { let s = String::from("hello"); let len = calculate_length(&s); }
mutable reference
rustfn change(s: &mut String) { s.push_str(", world"); } fn main() { let mut s = String::from("hello"); change(&mut s); }
- In the same scope, you can only have one mutable reference to a particular piece of data.
- mutable reference can't coexist with immutable reference.
- the scope of the reference is the last time it is used.
Non-lexical Lifetimes: used to find the lifetime of the reference
rustfn main() { let r; { let x = 5; r = &x; } println!("{}", r); // error, x is dropped }
Dangling Reference: a reference that refers to a location in memory that may have been given to someone else, so the data has been deallocated
rustfn dangle() -> &String { let s = String::from("hello"); &s }
Compound Data Type
String
create
rustlet s = String::from("hello");
update
rustlet mut s = String::from("hello"); s.push_str(", world"); s.push('!')
insert
rustlet mut s = String::from("hello"); s.insert(5, ','); s.insert_str(6, " world");
replace
rustlet mut s = String::from("hello, world"); s.replace("hello", "hi");
replacen
rustlet mut s = String::from("hello, world"); let s1 = s.replacen("hello", "hi", 1);
replace_range
rustlet mut s = String::from("hello, world"); s.replace_range(0..5, "hi");
pop
rustlet mut s = String::from("hello"); let x = s.pop(); // x is Some('o')
remove
rustlet mut s = String::from("hello"); let x = s.remove(0); // x is 'h'
truncate
rustlet mut s = String::from("hello"); s.truncate(3); // s is "hel"
clear
rustlet mut s = String::from("hello"); s.clear(); // s is ""
concatenate
rustlet s1 = String::from("hello"); let s2 = String::from("world"); let s3 = s1 + &s2; // s1 is invalid let s4 = format!("{}-{}", s1, s2); // s1 is valid
slice
rustlet s = String::from("hello, world"); let hello = &s[0..5];
- use
&s[..]
to get the whole string
- use
the index should be valid UTF-8 character boundary
rustlet s = String::from("你好"); let s1 = &s[0..1]; // error let s2 = &s[0..3]; // ok
- Char is Unicode type in Rust that is 4 bytes, but Char in String is UTF-8, so the length of the string is the number of bytes, not the number of characters, a character may be 1-4 bytes.
convert between string and &str
rustlet s = String::from("hello"); // "hello".to_string() let s1: &str = &s;
Escape Characters
rustlet s = "hello\nworld"; let s = r"hello\nworld"; // raw string let s = r#"hello "world""#; // include "
operate UTF-8 String
char
rustlet s = "你好"; let c = s.chars().nth(0); // Some('你')
bytes
rustlet s = "你好"; let b = s.bytes().nth(0); // Some(228)
traverse
rustlet s = "你好"; for c in s.chars() { println!("{}", c); }
Tuple
create
rustlet x: (i32, f64, u8) = (500, 6.4, 1);
destructure
rustlet (x, y, z) = x;
access
rustlet x = x.0;
return multiple values
rustfn get_point() -> (i32, i32) { (3, 5) }
Struct
define and create
ruststruct User { username: String, email: String, sign_in_count: u64, active: bool, } let user = User { username: String::from("someone"), email: String::from("[email protected]") sign_in_count: 1, active: true, };
simple create
rustfn build_user(username: String, email: String) -> User { User { username, email, sign_in_count: 1, active: true, } }
when the field name and the variable name are the same, you can use the shorthand
access
rustlet username = user.username;
update
rustlet user2 = User { username: String::from("someone2"), ..user };
Tuple Struct
ruststruct Color(i32, i32, i32); // no field name let black = Color(0, 0, 0);
Unit-Like Struct
ruststruct AlwaysActive; let always_active = AlwaysActive; // we don't care about the data, just the behavior impl AlwaysActive { fn is_active(&self) -> bool { true } }
Print Struct
rust#[derive(Debug)] struct User { username: String, email: String, sign_in_count: u64, active: bool, } println!("{:?}", user); // print to standard stdout println!("{:#?}", user); // pretty print dbg!(user); // Macro, print and return the value to stderr
Enum
define
rustenum IpAddr { V4(u8, u8, u8, u8), V6(String), }
create
rustlet home = IpAddr::V4(127, 0, 0, 1); let loopback = IpAddr::V6(String::from("::1"));
Assimilation Type
rustenum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), }
Option
rustenum Option<T> { Some(T), None, } let x = Some(5); let s = Some("hello") let y: Option<i32> = None; // must specify the type when use None
Array & Vector
define
rustlet a = [1, 2, 3, 4, 5]; let a: [i32; 5] = [1, 2, 3, 4, 5]; let a = [3; 5]; // [3, 3, 3, 3, 3]
from_fn
rustlet arr: [String; 8] = std::array::from_fn(|_i| String::from("hello"));
access
rustlet first = a[0];
slice
rustlet a = [1, 2, 3, 4, 5]; let slice = &a[1..3]; // [2, 3]
Control Flow
if
if
rustlet x = 5; if x < 5 { println!("less than 5"); } else if x == 5 { println!("equal to 5"); } else { println!("greater than 5"); }
loop
for
rustlet a = [10, 20, 30, 40, 50]; for element in a.iter() { println!("{}", element); } for idx in 0..5 { println!("{}", a[idx]); }
Method Equivalent Ownership for item in collection from item in IntoIterator::into_iter(collection) collection is moved for item in &collection from item in collection.iter() collection is borrowed for item in &mut collection from item in collection.iter_mut() collection is mutably borrowed enumerate
rustlet a = [10, 20, 30, 40, 50]; for (idx, element) in a.iter().enumerate() { println!("{}: {}", idx, element); }
continue
rustfor number in 1..=100 { if number % 3 == 0 { continue; } println!("{}", number); }
while
rustlet mut number = 3; while number != 0 { println!("{}", number); number -= 1; }
loop
rustlet mut number = 3; loop { println!("{}", number); number -= 1; if number == 0 { break; } }
Pattern Matching
match
match
rustlet number = 3; match number { 1 => println!("one"), 2 | 3 => println!("two or three"), 4..=10 => println!("four to ten"), _ => println!("other"), }
pattern destructuring
rustlet x = Some(5); match x { Some(i) => println!("{}", i), None => (), }
matches!
rustv.iter().filter(|x| matches!(x, Some(i) if i > 2)).count()
if let
if let
rustlet x = Some(5); if let Some(i) = x { println!("{}", i); }
when you only care about one pattern and ignore the rest, you can use
if let
while let
while let
rustlet mut v = vec![Some(1), Some(2), Some(3)]; while let Some(i) = v.pop() { println!("{}", i); }
Pattern Matching Examples
Method
define
ruststruct Rectangle { width: u32, height: u32, } impl Rectangle { new(width: u32, height: u32) -> Rectangle { Rectangle { width, height } } fn area(&self) -> u32 { self.width * self.height } } println!("The area of the rectangle is {} square pixels.", rect.area());
self
&self
: borrow self&mut self
: mutable borrow selfself
: take ownership of self
related function
rustimpl Rectangle { fn square(size: u32) -> Rectangle { Rectangle { width: size, height: size } } } let sq = Rectangle::square(3);
enum method
rustenum Option<T> { Some(T), None, } impl<T> Option<T> { fn unwrap(self) -> T { match self { Some(val) => val, None => panic!("called `Option::unwrap()` on a `None` value"), } } } let x = Some(5); let v = x.unwrap();
Generics
Generics
define
ruststruct Point<T> { x: T, y: T, } impl<T> Point<T> { fn x(&self) -> &T { &self.x } } let p = Point { x: 5, y: 10 };
the compiler will generate the code for each type that is used in the code(single monomorphization)
use multiple types
ruststruct Point<T, U> { x: T, y: U, } let p = Point { x: 5, y: 10.4 };
use in enum
rustenum Result<T, E> { Ok(T), Err(E), } enum Option<T> { Some(T), None, }
use in function
rustfn largest<T: PartialOrd + Copy>(list: &[T]) -> T { let mut largest = list[0]; for &item in list.iter() { if item > largest { largest = item; } } largest }
define method for specific type
rusttrait Summary { fn summarize(&self) -> String; } impl Summary for i32 { fn summarize(&self) -> String { format!("i32: {}", self) } } let x = 5; println!("{}", x.summarize());
Const Generics
define
ruststruct Array<T, const N: usize> { data: [T; N], } let a = Array { data: [1, 2, 3] };
Trait
define
rusttrait Summary { fn summarize(&self) -> String; } struct NewsArticle { headline: String, location: String, content: String, } impl Summary for NewsArticle { fn summarize(&self) -> String { format!("{}, by {} ({})", self.headline, self.location, self.content) } }
orphan rule: if a trait is defined for a type, the type or trait must be defined in the current crate at least one of them
default implement
rusttrait Summary { fn summarize(&self) -> String { String::from("Read more...") } } impl Summary for NewsArticle {} article.summarize();
use trait as parameter
rustfn notify(item: impl Summary) { println!("Breaking news! {}", item.summarize()); } fn notify<T: Summary>(item: T) { println!("Breaking news! {}", item.summarize()); }
multiple trait bounds
rustfn notify(item: impl Summary + Display) { println!("Breaking news! {}", item.summarize()); }
where clause
rustfn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 { 0 } fn some_function<T, U>(t: T, u: U) -> i32 where T: Display + Clone, U: Clone + Debug, { 0 }
use trait to implement method
ruststruct Pair<T> { x: T, y: T, } impl<T> Pair<T> { fn new(x: T, y: T) -> Self { Self { x, y } } } impl<T: Display + PartialOrd> Pair<T> { fn cmp_display(&self) { if self.x >= self.y { println!("The largest member is x = {}", self.x); } else { println!("The largest member is y = {}", self.y); } } }
return trait
rustfn returns_summarizable() -> impl Summary { NewsArticle { headline: String::from("Penguins win the Stanley Cup Championship!"), location: String::from("Pittsburgh, PA, USA"), content: String::from( "The Pittsburgh Penguins once again are the best \ hockey team in the NHL.", ), } }
examples
rustuse std::ops::Add; // use derive to implement Debug trait #[derive(Debug)] struct Point<T: Add<T, Output = T>> { // T must implement Add trait x: T, y: T, } impl<T: Add<T, Output = T>> Add for Point<T> { // implement Add trait for Point type Output = Point<T>; // define associated type fn add(self, p: Point<T>) -> Point<T> { // implement add method Point{ x: self.x + p.x, y: self.y + p.y, } } } fn add<T: Add<T, Output=T>>(a:T, b:T) -> T { // implement add function to add two values a + b } fn main() { let p1 = Point{x: 1.1f32, y: 1.1f32}; let p2 = Point{x: 2.1f32, y: 2.1f32}; println!("{:?}", add(p1, p2)); let p3 = Point{x: 1i32, y: 1i32}; let p4 = Point{x: 2i32, y: 2i32}; println!("{:?}", add(p3, p4)); }
trait object
define
rustpub trait Draw { fn draw(&self); } pub struct Button { pub width: u32, pub height: u32, pub label: String, } impl Draw for Button { fn draw(&self) { println!("Drawing a button: {}", self.label); } } pub struct SelectBox { pub width: u32, pub height: u32, pub options: Vec<String>, } impl Draw for SelectBox { fn draw(&self) { println!("Drawing a select box: {:?}", self.options); } } pub struct Screen { pub components: Vec<Box<dyn Draw>>, // Box is a smart pointer, can be replaced by Vec<&dyn Draw> } impl Screen { pub fn run(&self) { for component in self.components.iter() { component.draw(); } } }
dynamic dispatch
- use
Box<dyn Trait>
to store the trait object - use
&dyn Trait
to store the trait object reference
- use
Self and self
rustimpl Draw for Button { fn draw(&self) -> Self { // Self refers to the type that implements the trait or method self.clone() // self refers to the instance of Object } }
limit of trait object
- can't use generic type
- can't use associated type
- can't use Self
Advanced Trait
associated type
rustpub trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>; } pub struct Counter { count: u32, } impl Iterator for Counter { type Item = u32; fn next(&mut self) -> Option<Self::Item> { if self.count < 5 { self.count += 1; Some(self.count) } else { None } } }
default generic type
rusttrait Add<RHS = Self> { type Output; fn add(self, rhs: RHS) -> Self::Output; }
fully qualified syntax
rusttrait Pilot { fn fly(&self); } trait Wizard { fn fly(&self); } struct Human; impl Pilot for Human { fn fly(&self) { println!("This is your captain speaking."); } } impl Wizard for Human { fn fly(&self) { println!("Up!"); } } impl Human { fn fly(&self) { println!("*waving arms furiously*"); } } let person = Human; Pilot::fly(&person); Wizard::fly(&person); // if without self, the compiler can't determine which fly method to call //so need to use fully qualified syntax like <Type as Trait>::method person.fly();
super trait
rusttrait Pilot { fn fly(&self); } trait Wizard { fn fly(&self); } trait Greet: Pilot + Wizard { fn greet(&self) { self.fly(); } }
newtype
ruststruct Wrapper(Vec<String>); impl fmt::Display for Wrapper { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "[{}]", self.0.join(", ")) } }
Dynamic Sized Type
Vector
create
rustlet v: Vec<i32> = Vec::new(); let v = vec![1, 2, 3];
update
rustlet mut v = Vec::new(); v.push(5);
access
rustlet v = vec![1, 2, 3, 4, 5]; let third: &i32 = &v[2]; let third: Option<&i32> = v.get(2);
iterate
rustlet v = vec![100, 32, 57]; for i in &v { println!("{}", i); }
mutable iterate
rustlet mut v = vec![100, 32, 57]; for i in &mut v { *i += 50; }
enum
rustenum SpreadsheetCell { Int(i32), Float(f64), Text(String), } let row = vec![ SpreadsheetCell::Int(3), SpreadsheetCell::Text(String::from("blue")), SpreadsheetCell::Float(10.12), ];
trait
rusttrait IpAddr { fn print(&self); } struct V4(u8, u8, u8, u8); impl IpAddr for V4 { fn print(&self) { println!("{}.{}.{}.{}", self.0, self.1, self.2, self.3); } } struct V6(String); impl IpAddr for V6 { fn print(&self) { println!("{}", self.0); } } let v: Vec<Box<dyn IpAddr>> = vec![ Box::new(V4(127, 0, 0, 1)), Box::new(V6(String::from("::1"))), ];
examples
rustassert!(!v.is_empty()); v.insert(2, 10); assert_eq!(v.remove(2), 10); assert_eq!(v.pop(), Some(5)); v.clear(); let mut v1 = [1, 2].to_vec(); v.append(&mut v1); v.truncate(2); v.extend([1, 2].iter().cloned()); v.reserve(10); // reserve capacity v.shrink_to_fit(); // shrink the capacity to the length v.retain(|x| *x > 2); // remove element that not satisfy the condition let mut m: Vec<i32> = v.drain(1..3).collect(); // delete and return the deleted elements let v2 = m.split_off(1); // split the vector into two vectors let slice = &v[1..=3];
sort
rustlet mut v = vec![1, 5, 10, 2, 15]; v.sort(); v.sort_by(|a, b| b.cmp(a)); v.sort_unstable(); v.sort_unstable_by(|a, b| b.cmp(a));
HashMap
create
rustuse std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.insert(String::from("Yellow"), 50); // assign specific size let teams = HashMap::with_capacity(100); // use iterator and collect let teams = vec![(String::from("Blue"), 10), (String::from("Yellow"), 50)]; let scores: HashMap<_, _> = teams.into_iter().collect();
search
rustlet mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.insert(String::from("Yellow"), 50); let team_name = String::from("Blue"); let score = scores.get(&team_name);
iterate
rustlet mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.insert(String::from("Yellow"), 50); for (key, value) in &scores { println!("{}: {}", key, value); }
update
rustlet mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); let score = scores.entry(String::from("Blue")).or_insert(0); *score += 1;
or_insert will return a mutable reference to the value, so you can update the value
Lifetime
define
rustfn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }
lifetime marker don't change the lifetime of the reference
rustlet string1 = String::from("long string is long"); let result; { let string2 = String::from("xyz"); result = longest(string1.as_str(), string2.as_str()); } println!("The longest string is {}", result);
the lifetime of the reference is the same as the lifetime of the shorter string, not the longer string
lifetime in struct
ruststruct ImportantExcerpt<'a> { part: &'a str, } let novel = String::from("Call me Ishmael. Some years ago..."); let first_sentence = novel.split('.').next().expect("Could not find a '.'"); let i = ImportantExcerpt { part: first_sentence };
lifetime elision
- each parameter that is a reference gets its own lifetime parameter
- if there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters
- if there are multiple input lifetime parameters, but one of them is
&self
or&mut self
, the lifetime ofself
is assigned to all output lifetime parameters - if there are multiple input lifetime parameters, and none of them are
&self
or&mut self
, an error will be thrown
lifetime in method
rustimpl<'a> ImportantExcerpt<'a> { fn level(&self) -> i32 { 3 } }
static lifetime
rustlet s: &'static str = "I have a static lifetime.";
Return and Error Handling
panic
rustpanic!("crash and burn");
unwrap
rustlet f = File::open("hello.txt").unwrap(); // success or panic
expect
rustlet f = File::open("hello.txt").expect("Failed to open hello.txt"); // success or panic with message
Result
rustuse std::fs::File; fn read_username_from_file() -> Result<String, io::Error> { let f = File::open("hello.txt"); let mut f = match f { Ok(file) => file, Err(e) => return Err(e), }; let mut s = String::new(); match f.read_to_string(&mut s) { Ok(_) => Ok(s), Err(e) => Err(e), } }
?
rustuse std::fs::File; fn read_username_from_file() -> Result<String, io::Error> { let mut f = File::open("hello.txt")?; let mut s = String::new(); f.read_to_string(&mut s)?; Ok(s) }
chain
rustuse std::fs::File; fn read_username_from_file() -> Result<String, io::Error> { let mut s = String::new(); File::open("hello.txt")?.read_to_string(&mut s)?; Ok(s) }
Package and Module
define
package: a Cargo feature that lets you build, test, and share crates
workspace: a directory that contains multiple packages
crate: a binary or library
module: a collection of items, such as functions, structs, traits, implementations, and modules
module
rustmod sound { mod instrument { fn clarinet() { // function body } } }
use
rustuse sound::instrument::clarinet;
as
rustuse std::io::Result as IoResult;
pub use
rustpub use sound::instrument;
comment
line comment
rust// line comment
block comment
rust/* block comment */
doc line comment
rust/// doc comment
doc block comment
rust/** doc comment */
doc test
rust/// ``` /// let x = 5; /// let y = 10; /// /// assert_eq!(add(x, y), 15); /// ``` fn add(x: i32, y: i32) -> i32 { x + y }
package line comment
rust//! package line comment
package block comment
rust/*! package block comment */
example
rust#![allow(unused)] fn main() { //! # Art //! //! 未来的艺术建模库,现在的调色库 pub use self::kinds::PrimaryColor; pub use self::kinds::SecondaryColor; pub use self::utils::mix; pub mod kinds { //! 定义颜色的类型 /// 主色 pub enum PrimaryColor { Red, Yellow, Blue, } /// 副色 #[derive(Debug,PartialEq)] pub enum SecondaryColor { Orange, Green, Purple, } } pub mod utils { //! 实用工具,目前只实现了调色板 use crate::kinds::*; /// 将两种主色调成副色 /// ```rust /// use art::utils::mix; /// use art::kinds::{PrimaryColor,SecondaryColor}; /// assert!(matches!(mix(PrimaryColor::Yellow, PrimaryColor::Blue), SecondaryColor::Green)); /// ``` pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor { SecondaryColor::Green } } }
Formatting
simple format
rust#![allow(unused)] fn main() { println!("Hello"); // => "Hello" println!("Hello, {}!", "world"); // => "Hello, world!" println!("The number is {}", 1); // => "The number is 1" println!("{:?}", (3, 4)); // => "(3, 4)" println!("{value}", value=4); // => "4" println!("{} {}", 1, 2); // => "1 2" println!("{:04}", 42); // => "0042" with leading zeros }
format!
rustlet s = format!("{}-{}", 1, 2);
eprint!, eprintln!
rusteprint!("error: {}", "error message"); eprintln!("error: {}", "error message");
positional arguments
rustprintln!("{0}, this is {1}. {1}, this is {0}", "Alice", "Bob"); // => "Alice, this is Bob. Bob, this is Alice"
named arguments
rustprintln!("{subject} {verb} {object}", object="the lazy dog", subject="the quick brown fox", verb="jumps over"); // => "the quick brown fox jumps over the lazy dog"
named arguments must be used after positional arguments