所有權:
變量具有唯一所有權。如果一個類型擁有 Copy
trait,一個舊的變量在將其賦值給其他變量后仍然可用。除此之外,賦值意味着轉移所有權。Rust 不允許自身或其任何部分實現了 Drop
trait 的類型使用 Copy
trait。
如下是一些 Copy
的類型:
- 所有整數類型,比如
u32
。 - 布爾類型,
bool
,它的值是true
和false
。 - 所有浮點數類型,比如
f64
。 - 字符類型,
char
。 - 元組,當且僅當其包含的類型也都是
Copy
的時候。比如,(i32, i32)
是Copy
的,但(i32, String)
就不是。
引用:
引用指的是獲取對象或變量的內容而不獲取所有權。也意味着不能修改被引用的值。可以存在多個引用。
let s1 = Stri與使用&
引用相反的操作是 解引用(dereferencing),它使用解引用運算符,*
ng::from("hello"); let len = calculate_length(&s1); //引用
與使用 &
引用相反的操作是 解引用(dereferencing),它使用解引用運算符 *
可變引用:
為了對引用的對象修改,可以在特定作用域中對特定數據僅存在唯一可變引用。
let mut s = String::from("hello"); let r1 = &mut s; let r2 = &mut s; //Error
但,需要注意的是,不能在擁有不可變引用的同時擁有可變引用。即,要么引用,要么可變引用。
let mut s = String::from("hello"); let r1 = &s; // no problem let r2 = &s; // no problem let r3 = &mut s; // Error, BIG PROBLEM
Slice:
數組是同一類型的對象的集合 T
, 存儲在連續內存中。 用方括號 []
創建數組, 以及它們的大小在編譯的時候判定,是它們的類型簽名的一部分 [T; size]
切片和數組相似,但它們的大小在編譯時是不知道的. 相反,切片是一個雙字對象,第一個字是一個指針中的數據,第二個字是切片的長度。切片可借用數組的截面,並具有式簽名 &[T]
結構體(struct):
struct User { username: String, email: String, sign_in_count: u64, active: bool, }
字段初始化簡寫語法(field init shorthand):
fn build_user(email: String, username: String) -> User { User { email, username, active: true, sign_in_count: 1, } }
結構體更新語法(struct update syntax)實現從其他結構體創建實例:
let user2 = User { email: String::from("another@example.com"), username: String::from("anotherusername567"), ..user1 };
使用結構體更新語法為一個 User
實例設置新的 email
和 username
值,不過其余值來自 user1
變量中實例的字段
元組結構體(tuple structs)
元組結構體有着結構體名稱提供的含義,但沒有具體的字段名,只有字段的類型。當你想給整個元組取一個名字,並使元組成為與其他元組不同的類型時,元組結構體是很有用的,這時像常規結構體那樣為每個字段命名就顯得多余和形式化了。
struct Color(i32, i32, i32); struct Point(i32, i32, i32); let black = Color(0, 0, 0); let origin = Point(0, 0, 0);
因為它們是不同的元組結構體的實例。你定義的每一個結構體有其自己的類型,即使結構體中的字段有着相同的類型。例如,一個獲取 Color
類型參數的函數不能接受 Point
作為參數,即便這兩個類型都由三個 i32
值組成。在其他方面,元組結構體實例類似於元組:可以將其解構為單獨的部分,也可以使用 .
后跟索引來訪問單獨的值,
類單元結構體(unit-like structs)
沒有任何字段,類似於 ()
,即 unit 類型。類單元結構體常常在你想要在某個類型上實現 trait 但不需要在類型中存儲數據的時候發揮作用。
結構體數據的所有權
可以使結構體存儲被其他對象擁有的數據的引用,不過這么做的話需要用上 生命周期(lifetimes)。
struct 類型友好打印:
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } fn main() { let rect1 = Rectangle { width: 30, height: 50 }; println!("rect1 is {:#?}", rect1); }
方法 與函數類似:它們使用 fn
關鍵字和名稱聲明,可以擁有參數和返回值,同時包含在某處調用該方法時會執行的代碼。不過方法與函數是不同的,因為它們在結構體的上下文中被定義(或者是枚舉或 trait 對象的上下文),並且它們第一個參數總是 self
,它代表調用該方法的結構體實例。
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } //方法 impl Rectangle { fn area(&self) -> u32 { self.width * self.height } } fn main() { let rect1 = Rectangle { width: 30, height: 50 }; println!( "The area of the rectangle is {} square pixels.", rect1.area() ); }
在 area
的簽名中,使用 &self
來替代 rectangle: &Rectangle
,因為該方法位於 impl Rectangle
上下文中所以 Rust 知道 self
的類型是 Rectangle
。注意仍然需要在 self
前面加上 &
,就像 &Rectangle
一樣。方法可以選擇獲取 self
的所有權,或者像我們這里一樣不可變地借用 self
,或者可變地借用 self
,就跟其他參數一樣。
這里選擇 &self
的理由跟在函數版本中使用 &Rectangle
是相同的:我們並不想獲取所有權,只希望能夠讀取結構體中的數據,而不是寫入。如果想要在方法中改變調用方法的實例,需要將第一個參數改為 &mut self
。通過僅僅使用 self
作為第一個參數來使方法獲取實例的所有權是很少見的;這種技術通常用在當方法將 self
轉換成別的實例的時候,這時我們想要防止調用者在轉換之后使用原始的實例。
關聯函數
impl
塊的另一個有用的功能是:允許在 impl
塊中定義 不 以 self
作為參數的函數。這被稱為 關聯函數(associated functions),因為它們與結構體相關聯。它們仍是函數而不是方法,因為它們並不作用於一個結構體的實例。你已經使用過 String::from
關聯函數了。
關聯函數經常被用作返回一個結構體新實例的構造函數。例如我們可以提供一個關聯函數,它接受一個維度參數並且同時作為寬和高,這樣可以更輕松的創建一個正方形 Rectangle
而不必指定兩次同樣的值:
impl Rectangle { fn square(size: u32) -> Rectangle { Rectangle { width: size, height: size } } }
使用結構體名和 ::
語法來調用這個關聯函數:比如 let sq = Rectangle::square(3);
。這個方法位於結構體的命名空間中:::
語法用於關聯函數和模塊創建的命名空間。
每個結構體都允許擁有多個 impl
塊。
枚舉:
舉例:
enum IpAddrKind { V4, V6, }
可以像這樣創建 IpAddrKind
兩個不同成員的實例:
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
Reference :enum
下邊的IpAddr
枚舉的新定義表明了 V4
和 V6
成員都關聯了 String
值:
enum IpAddr { V4(String), V6(String), } let home = IpAddr::V4(String::from("127.0.0.1")); let loopback = IpAddr::V6(String::from("::1"));
我們直接將數據附加到枚舉的每個成員上,這樣就不需要一個額外的結構體了。
用枚舉替代結構體還有另一個優勢:每個成員可以處理不同類型和數量的數據。IPv4 版本的 IP 地址總是含有四個值在 0 和 255 之間的數字部分。如果我們想要將 V4
地址存儲為四個 u8
值而 V6
地址仍然表現為一個 String
,這就不能使用結構體了。枚舉則可以輕易處理的這個情況:
enum IpAddr { V4(u8, u8, u8, u8), V6(String), } let home = IpAddr::V4(127, 0, 0, 1); let loopback = IpAddr::V6(String::from("::1"));
另一個示例:
enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), }
這個枚舉有四個含有不同類型的成員:
Quit
沒有關聯任何數據。Move
包含一個匿名結構體。Write
包含單獨一個String
。ChangeColor
包含三個i32
結構體和枚舉還有另一個相似點:就像可以使用 impl
來為結構體定義方法那樣,也可以在枚舉上定義方法。這是一個定義於我們 Message
枚舉上的叫做 call
的方法:
impl Message { fn call(&self) { // 在這里定義方法體 } } let m = Message::Write(String::from("hello")); m.call();
方法體使用了 self
來獲取調用方法的值。這個例子中,創建了一個值為 Message::Write(String::from("hello"))
的變量 m
,而且這就是當 m.call()
運行時 call
方法中的 self
的值。
Option
Option是標准庫定義的另一個枚舉。Rust 並沒有空值,不過它確實擁有一個可以編碼存在或不存在概念的枚舉。這個枚舉是 Option<T>
,而且它定義於標准庫中,如下:
enum Option<T> { Some(T), None, }
Option<T>
枚舉是如此有用以至於它甚至被包含在了 prelude 之中,你不需要將其顯式引入作用域。另外,它的成員也是如此,可以不需要 Option::
前綴來直接使用 Some
和 None
。即便如此 Option<T>
也仍是常規的枚舉,Some(T)
和 None
仍是 Option<T>
的成員。
<T>
語法是一個泛型類型參數。<T>
意味着 Option
枚舉的 Some
成員可以包含任意類型的數據。這里是一些包含數字類型和字符串類型 Option
值的例子:
let some_number = Some(5); let some_string = Some("a string"); let absent_number: Option<i32> = None;
如果使用 None
而不是 Some
,需要告訴 Rust Option<T>
是什么類型的,因為編譯器只通過 None
值無法推斷出 Some
成員保存的值的類型。
當有一個 Some
值時,我們就知道存在一個值,而這個值保存在 Some
中。當有個 None
值時,在某種意義上,它跟空值具有相同的意義:並沒有一個有效的值。
>Option<T>
比空值要好的原因是因為 Option<T>
和 T
(這里 T
可以是任何類型)是不同的類型,編譯器不允許像一個肯定有效的值那樣使用 Option<T>
。(也就是不能混用,要使用空類型,必須使用Option<T>,而非此類型的變量,永遠非空,不需要做空值檢查。)