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
仍會受到借用限制,借出後不能夠將其他欄位借出。