Rust 中常使用「移動 (move)」語意,為使複製成本降低,大部分的函式會採用 pass-by-reference 的方式 &T,但是移動語意的好處在於可以提前刪除物件。
標準庫提供的 enum 類型 {std, alloc}::borrow::Cow 同時允許「借出的 (Borrowed)」與「擁有的 (Owned)」兩種資料型態,並且會在借出可變引用 &mut T 時複製,亦可單純借出唯讀引用。
pub enum Cow<'a, B>
where
B: 'a + ToOwned + ?Sized,
{
Borrowed(&'a B),
Owned(<B as ToOwned>::Owned),
}要能複製必須先實作 Clone trait,所以這邊加入了一個輔助的 ToOwned trait,它協助一般類型調用 Clone::clone() 與幫助無大小類型建立它們的有大小類型,無大小類型將在下面的小節介紹。另外,這邊用了一個 Borrow trait,跟 AsRef 非常類似,也在下面的小節介紹。
pub trait ToOwned {
type Owned: Borrow<Self>;
fn to_owned(&self) -> Self::Owned;
fn clone_into(&self, target: &mut Self::Owned) { ... }
}
impl<T: Clone> ToOwned for T { ... }當一個函式想同時允許 T 與 &T 輸入時,可以使用 Into trait 轉成 Cow。
fn foo<'a, C>(c: C)
where
C: Into<Cow<'a, str>>,
{
let s = c.into();
s.as_ref(); // 唯讀借用
s.to_mut(); // 可變借用
let s: String = s.into_owned(); // 複製 (借出) or 移動 (擁有),產生擁有類型
}
foo("abc"); // &str
foo("abc".to_string()); // String無大小類型是一個抽象的概念,通常基於動態長度設計--也就是 slice [T] 類型,雖然知道 T 的大小,但是長度是 runtime 期間儲存的,能用 <[T]>::len(&self) -> usize 函式查詢。有了上述概念,&[T] 是一個指在第一項的指標 &T,和一個長度 usize 組成的。
設計無大小類型的目的在於,可以區分「記憶體內的資料」和「與程式綁定的資料」。例如我們可以使用 const 建立定值,程式會將定值資料複製到記憶體中開始執行,但是使用「指標」的定值,就好像只複製指標到記憶體一樣,由於資料是程式的一部份,指標會指向程式本身的區段,不用額外增加記憶體。
// 定值資料、定值指標
const SLICE: &[u8] = &[1, 2, 3];
const TITLE: &str = "Hello!";
// 複製定值指標到動態指標
let slice = SLICE;
// 複製定值指標到動態可變指標
let mut slice = SLICE;
// 定值資料、動態指標
let s = &[1, 2, 3];
let title = "Hello!";只要自訂類型包含無大小類型,該類型就會是 !Sized。除了 trait 自身的定義,一般泛型參數都預設為 Sized,使用 ?Sized 則可以同時允許無大小類型。
Rust 標準庫包含下列無大小類型:
!Sized |
「擁有的」類型 | 用途 |
|---|---|---|
[T] |
Vec<T> |
動態陣列,比靜態陣列 [T; N] 靈活 |
str |
String |
UTF-8 合格字串,底層為 [u8] |
std::ffi::OsStr |
std::ffi::OsString |
系統預設字串,底層為 [u8],可能不相容 UTF-8 |
std::path::Path |
std::path::PathBuf |
檔案路徑,底層為 OsStr |
Rust 本身包含三種指標轉換的 trait,並且各自有其可變版本,如下表所列:
| 唯讀 | 可變 | 關係 | 用途 |
|---|---|---|---|
std::borrow::Borrow |
BorrowMut |
A: Borrow<B>, &A -> &B |
底層類型 B 的指標轉換 |
std::convert::AsRef |
AsMut |
A: AsRef<B>, &A -> &B |
任意指標的輕量級轉換 |
std::ops::Deref |
DerefMut |
A: Deref<Target=B>, &A -> &B |
自動指標轉換,可實現類似繼承的效果 |
雖然 Borrow 跟 AsRef 的效果幾乎一樣,但是 Borrow 比較專注於保持底層類型的相容性,而 AsRef 則類似於指標界的 Into。
由於 Deref 是自動的,因此 &A -> &B 的關係是唯一的,不過可以連鎖呼叫 &A -> &B -> &C。
另外注意它們都是函式,所以結構體 struct 仍會受到借用限制,借出後不能夠將其他欄位借出。