已發布·持續改進
Rust 指南 · 3/6
本章目前僅提供英文版。
Rust is statically typed: every value has a type known at compile time, and even numeric types never convert silently. This chapter tours the primitive types, the two kinds of strings, structs and enums, the everyday collections, and Option, Rust's answer to null.
| Kind | Types | Notes |
|---|---|---|
| Signed integers | i8 i16 i32 i64 i128 isize | i32 is the default |
| Unsigned integers | u8 u16 u32 u64 u128 usize | indexes and lengths use usize |
| Floating point | f32 f64 | f64 is the default |
| Boolean | bool | true or false |
| Character | char | one Unicode scalar value, 4 bytes |
Integer overflow panics in debug builds and wraps around by default in release builds. When overflow is a real possibility, say what you mean with checked_add, wrapping_add or saturating_add. Conversions are explicit with as.
fn main() {
let a: i32 = -42;
let b: u8 = 255;
let c = 3.14_f64;
let ok: bool = true;
let heart: char = '♥';
println!("{a} {b} {c} {ok} {heart}");
println!("{:?}", b.checked_add(1)); // None on overflow
println!("{}", b.wrapping_add(1)); // 0, wraps around
println!("{}", b.saturating_add(1)); // 255, clamps at the max
let n = 300_i32 as u8; // 44, `as` truncates
let avg = (7 as f64) / 2.0; // 3.5
println!("{n} {avg}");
}A tuple groups a fixed number of values of different types; an array holds a fixed number of values of the same type. For a list that grows, use Vec, covered below.
fn main() {
let point: (i32, f64, char) = (1, 2.5, 'x');
let (x, y, z) = point; // destructuring
println!("{x} {y} {z} / first field {}", point.0);
let nums = [1, 2, 3, 4, 5];
let zeros = [0u8; 4]; // four zeros
let slice = &nums[1..3]; // [2, 3]
println!("{} {:?} {:?}", nums.len(), zeros, slice);
}Indexing past the end of an array panics instead of reading random memory. When an index might be out of range, use nums.get(10), which returns an Option.
There are two main string types. String is an owned, growable, heap-allocated buffer. &str is a borrowed view into UTF-8 text that lives somewhere else; string literals are &'static str. Taking &str as a parameter lets callers pass either kind.
fn greet(name: &str) -> String {
format!("Hello, {name}")
}
fn main() {
let literal: &str = "Rust";
let mut owned = String::from("Hello");
owned.push_str(", world");
owned.push('!');
println!("{}", greet(literal));
println!("{}", greet(&owned)); // &String coerces to &str
let part: &str = &owned[0..5]; // "Hello"
println!("{part}");
// len() counts bytes; chars().count() counts characters
println!("{} {}", "héllo".len(), "héllo".chars().count()); // 6 5
}Strings are UTF-8, so s[0] is not allowed. Use chars() for characters, and keep range slices on character boundaries.
A struct bundles related data under named fields. Methods live in an impl block: &self means the method only reads, &mut self means it modifies. Functions without self, like new, are associated functions and are called as Rect::new.
#[derive(Debug, Clone, PartialEq)]
struct Rect {
width: u32,
height: u32,
}
impl Rect {
fn new(width: u32, height: u32) -> Self {
Self { width, height }
}
fn area(&self) -> u32 {
self.width * self.height
}
fn scale(&mut self, k: u32) {
self.width *= k;
self.height *= k;
}
}
fn main() {
let mut r = Rect::new(3, 4);
println!("area {}", r.area()); // 12
r.scale(2);
println!("{r:?}"); // Rect { width: 6, height: 8 }
}An enum models "exactly one of these", and each variant can carry its own data. Paired with match, the compiler makes sure every variant is handled.
enum Shape {
Circle { radius: f64 },
Square(f64),
Point,
}
fn area(shape: &Shape) -> f64 {
match shape {
Shape::Circle { radius } => std::f64::consts::PI * radius * radius,
Shape::Square(side) => side * side,
Shape::Point => 0.0,
}
}
fn main() {
let shapes = [Shape::Circle { radius: 1.0 }, Shape::Square(2.0), Shape::Point];
for s in &shapes {
println!("{:.2}", area(s));
}
}Rust has no null. A value that may be missing has type Option<T>, which is either Some(value) or None. You must deal with both cases before using the value, which rules out null-pointer errors entirely.
use std::collections::HashMap;
fn main() {
let mut scores: Vec<i32> = Vec::new();
scores.push(80);
scores.push(95);
let total: i32 = scores.iter().sum();
println!("{scores:?} total {total}");
println!("{:?}", scores.get(5)); // None
let text = "the cat the dog the end";
let mut counts: HashMap<&str, usize> = HashMap::new();
for word in text.split_whitespace() {
*counts.entry(word).or_insert(0) += 1;
}
match counts.get("the") {
Some(n) => println!("the: {n} times"), // 3
None => println!("not found"),
}
let birds = counts.get("bird").copied().unwrap_or(0);
println!("bird: {birds} times"); // 0
}Vec<T> is a growable array (also built with vec![1, 2, 3]). HashMap<K, V> maps keys to values; its entry API makes "insert or update" a one-liner. Combinators like map, unwrap_or and and_then often replace a full match on Option.
as.String owns its text, &str borrows it; prefer &str for parameters.impl methods; enums express "one of several cases".Vec and HashMap are the workhorse collections, and Option replaces null.
0 則留言
登入 · 登入後即可留言。
來留下第一則留言吧。