目錄
1、什么是所有權(quán)?
1.1?所有權(quán)規(guī)則
?1.2 變量作用域
1.3 String 類型
1.4 內(nèi)存與分配
變量與數(shù)據(jù)交互的方式(一):移動(dòng)
變量與數(shù)據(jù)交互的方式(二):克隆
只在棧上的數(shù)據(jù):拷貝
1.5 所有權(quán)與函數(shù)
1.6 返回值與作用域
1、什么是所有權(quán)?
所有權(quán)(系統(tǒng))是 Rust 最為與眾不同的特性,對(duì)語(yǔ)言的其他部分有著深刻含義。它讓 Rust 無(wú)需垃圾回收(garbage collector)即可保障內(nèi)存安全,因此理解 Rust 中所有權(quán)如何工作是十分重要的。
所有程序都必須管理其運(yùn)行時(shí)使用計(jì)算機(jī)內(nèi)存的方式。一些語(yǔ)言中具有垃圾回收機(jī)制,在程序運(yùn)行時(shí)有規(guī)律地尋找不再使用的內(nèi)存;在另一些語(yǔ)言中,程序員必須親自分配和釋放內(nèi)存。Rust 則選擇了第三種方式:通過(guò)所有權(quán)系統(tǒng)管理內(nèi)存,編譯器在編譯時(shí)會(huì)根據(jù)一系列的規(guī)則進(jìn)行檢查。如果違反了任何這些規(guī)則,程序都不能編譯。在運(yùn)行時(shí),所有權(quán)系統(tǒng)的任何功能都不會(huì)減慢程序。
因?yàn)樗袡?quán)對(duì)很多程序員來(lái)說(shuō)都是一個(gè)新概念,需要一些時(shí)間來(lái)適應(yīng)。好消息是隨著你對(duì) Rust 和所有權(quán)系統(tǒng)的規(guī)則越來(lái)越有經(jīng)驗(yàn),你就越能自然地編寫(xiě)出安全和高效的代碼。
1.1?所有權(quán)規(guī)則
首先,讓我們看一下所有權(quán)的規(guī)則。當(dāng)我們通過(guò)舉例說(shuō)明時(shí),請(qǐng)謹(jǐn)記這些規(guī)則:
- Rust 中的每一個(gè)值都有一個(gè)?所有者(owner)。
- 值在任一時(shí)刻有且只有一個(gè)所有者。
- 當(dāng)所有者(變量)離開(kāi)作用域,這個(gè)值將被丟棄。
?1.2 變量作用域
在所有權(quán)的第一個(gè)例子中,我們看看一些變量的?作用域(scope)。作用域是一個(gè)項(xiàng)(item)在程序中有效的范圍。假設(shè)有這樣一個(gè)變量:
let a = 12345
變量 a綁定到了一個(gè)字符串字面值,這個(gè)字符串值是硬編碼進(jìn)程序代碼中的。這個(gè)變量從聲明的點(diǎn)開(kāi)始直到當(dāng)前?作用域?結(jié)束時(shí)都是有效的。
fn main() { // 作用域開(kāi)始處
let a = 12345; // 此時(shí),a才開(kāi)始分配存儲(chǔ)空間
} // 作用域開(kāi)始處
其實(shí)變量是否有效與作用域的關(guān)系跟其他編程語(yǔ)言是類似的。
1.3 String 類型
我們已經(jīng)見(jiàn)過(guò)字符串字面值,即被硬編碼進(jìn)程序里的字符串值。字符串字面值是很方便的,不過(guò)它們并不適合使用文本的每一種場(chǎng)景。原因之一就是它們是不可變的。另一個(gè)原因是并非所有字符串的值都能在編寫(xiě)代碼時(shí)就知道:例如,要是想獲取用戶輸入并存儲(chǔ)該怎么辦呢?為此,Rust 有第二個(gè)字符串類型,String
。這個(gè)類型管理被分配到堆上的數(shù)據(jù),所以能夠存儲(chǔ)在編譯時(shí)未知大小的文本。可以使用?from
?函數(shù)基于字符串字面值來(lái)創(chuàng)建?String
,如下:
fn main() {
let s = String::from("hello");
println!("{s}")
}
這兩個(gè)冒號(hào)?::
?是運(yùn)算符,允許將特定的?from
?函數(shù)置于?String
?類型的命名空間(namespace)下,而不需要使用類似?string_from
?這樣的名字。
可以修改此類字符串可變:
fn main() {
let mut s = String::from("hello ");
s.push('w');
s.push_str("orld");
println!("{s}")
}
那么這里有什么區(qū)別呢?為什么?String
?可變而字面值卻不行呢?區(qū)別在于兩個(gè)類型對(duì)內(nèi)存的處理上。
1.4 內(nèi)存與分配
就字符串字面值來(lái)說(shuō),我們?cè)诰幾g時(shí)就知道其內(nèi)容,所以文本被直接硬編碼進(jìn)最終的可執(zhí)行文件中。這使得字符串字面值快速且高效。不過(guò)這些特性都只得益于字符串字面值的不可變性。不幸的是,我們不能為了每一個(gè)在編譯時(shí)大小未知的文本而將一塊內(nèi)存放入二進(jìn)制文件中,并且它的大小還可能隨著程序運(yùn)行而改變。
對(duì)于?String
?類型,為了支持一個(gè)可變,可增長(zhǎng)的文本片段,需要在堆上分配一塊在編譯時(shí)未知大小的內(nèi)存來(lái)存放內(nèi)容。這意味著:
- 必須在運(yùn)行時(shí)向內(nèi)存分配器(memory allocator)請(qǐng)求內(nèi)存。
- 需要一個(gè)當(dāng)我們處理完?
String
?時(shí)將內(nèi)存返回給分配器的方法。
第一部分由我們完成:當(dāng)調(diào)用?String::from
?時(shí),它的實(shí)現(xiàn) (implementation) 請(qǐng)求其所需的內(nèi)存。這在編程語(yǔ)言中是非常通用的。
然而,第二部分實(shí)現(xiàn)起來(lái)就各有區(qū)別了。在有?垃圾回收(garbage collector,GC)的語(yǔ)言中,GC 記錄并清除不再使用的內(nèi)存,而我們并不需要關(guān)心它。在大部分沒(méi)有 GC 的語(yǔ)言中,識(shí)別出不再使用的內(nèi)存并調(diào)用代碼顯式釋放就是我們的責(zé)任了,跟請(qǐng)求內(nèi)存的時(shí)候一樣。從歷史的角度上說(shuō)正確處理內(nèi)存回收曾經(jīng)是一個(gè)困難的編程問(wèn)題。如果忘記回收了會(huì)浪費(fèi)內(nèi)存。如果過(guò)早回收了,將會(huì)出現(xiàn)無(wú)效變量。如果重復(fù)回收,這也是個(gè) bug。我們需要精確的為一個(gè)?allocate
?配對(duì)一個(gè)?free
。
Rust 采取了一個(gè)不同的策略:內(nèi)存在擁有它的變量離開(kāi)作用域后就被自動(dòng)釋放。
這是一個(gè)將?String
?需要的內(nèi)存返回給分配器的很自然的位置:當(dāng)變量離開(kāi)作用域,Rust 為我們調(diào)用一個(gè)特殊的函數(shù)。這個(gè)函數(shù)叫做?drop,在這里?String
?的作者可以放置釋放內(nèi)存的代碼。Rust 在結(jié)尾的?}
?處自動(dòng)調(diào)用?drop
。
注意:在 C++ 中,這種 item 在生命周期結(jié)束時(shí)釋放資源的模式有時(shí)被稱作?資源獲取即初始化(Resource Acquisition Is Initialization (RAII))。如果你使用過(guò) RAII 模式的話應(yīng)該對(duì) Rust 的?
drop
?函數(shù)并不陌生。
變量與數(shù)據(jù)交互的方式(一):移動(dòng)
fn main() {
let x = 5;
let y = x;
}
定義的2個(gè)簡(jiǎn)單變量,會(huì)直接存儲(chǔ)在棧中,因?yàn)樗鼈兊拈L(zhǎng)度都是固定的。
fn main() {
let s1 = String::from("hello");
let s2 = s1;
}
當(dāng)s1、s2存儲(chǔ)的時(shí)候,會(huì)在堆中開(kāi)辟一片區(qū)域用來(lái)存儲(chǔ),s1和s2 則通過(guò)指針的方式,來(lái)指向堆中存儲(chǔ)值得位置,這樣可以避免在堆中多次開(kāi)辟存儲(chǔ)空間,如果你在其他語(yǔ)言中聽(tīng)說(shuō)過(guò)術(shù)語(yǔ)?淺拷貝(shallow copy)和?深拷貝(deep copy),那么拷貝指針、長(zhǎng)度和容量而不拷貝數(shù)據(jù)可能聽(tīng)起來(lái)像淺拷貝。不過(guò)因?yàn)?Rust 同時(shí)使第一個(gè)變量無(wú)效了,這個(gè)操作被稱為?移動(dòng)(move),而不是叫做淺拷貝。上面的例子可以解讀為?s1
?被?移動(dòng)?到了?s2
?中。如果打印s1會(huì)出現(xiàn)以下錯(cuò)誤:
?
變量與數(shù)據(jù)交互的方式(二):克隆
?如果我們?確實(shí)?需要深度復(fù)制?String
?中堆上的數(shù)據(jù),而不僅僅是棧上的數(shù)據(jù),可以使用一個(gè)叫做?clone
?的通用函數(shù)。
以下是一個(gè)示例:
fn main() {
let s1 = String::from("hello ");
let s2 = s1.clone();
println!("{s1} {s2}")
}
打印以下看下結(jié)果:
只在棧上的數(shù)據(jù):拷貝
fn main() {
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y);
}
?Rust 有一個(gè)叫做?Copy
?trait 的特殊注解,可以用在類似整型這樣的存儲(chǔ)在棧上的類型上。如果一個(gè)類型實(shí)現(xiàn)了?Copy
?trait,那么一個(gè)舊的變量在將其賦值給其他變量后仍然可用。
那么哪些類型實(shí)現(xiàn)了Copy Trait呢,有以下類型:
- 所有整數(shù)類型,比如?
u32
。 - 布爾類型,
bool
,它的值是?true
?和?false
。 - 所有浮點(diǎn)數(shù)類型,比如?
f64
。 - 字符類型,
char
。 - 元組,當(dāng)且僅當(dāng)其包含的類型也都實(shí)現(xiàn)?
Copy
?的時(shí)候。比如,(i32, i32)
?實(shí)現(xiàn)了?Copy
,但?(i32, String)
?就沒(méi)有。
1.5 所有權(quán)與函數(shù)
將值傳遞給函數(shù)與給變量賦值的原理相似。向函數(shù)傳遞值可能會(huì)移動(dòng)或者復(fù)制,就像賦值語(yǔ)句一樣。
一個(gè)示例:
fn main() {
let s = String::from("hello"); // s 進(jìn)入作用域
takes_ownership(s); // s 的值移動(dòng)到函數(shù)里 ...
// ... 所以到這里不再有效
let x = 5; // x 進(jìn)入作用域
makes_copy(x); // x 應(yīng)該移動(dòng)函數(shù)里,
// 但 i32 是 Copy 的,
// 所以在后面可繼續(xù)使用 x
} // 這里,x 先移出了作用域,然后是 s。但因?yàn)?s 的值已被移走,
// 沒(méi)有特殊之處
fn takes_ownership(some_string: String) { // some_string 進(jìn)入作用域
println!("{}", some_string);
} // 這里,some_string 移出作用域并調(diào)用 `drop` 方法。
// 占用的內(nèi)存被釋放
fn makes_copy(some_integer: i32) { // some_integer 進(jìn)入作用域
println!("{}", some_integer);
} // 這里,some_integer 移出作用域。沒(méi)有特殊之處
可以發(fā)現(xiàn)在值傳遞給函數(shù)的時(shí)候,堆里的數(shù)據(jù)會(huì)移除當(dāng)前作用域,例如,s傳遞takes_ownership方法里,在當(dāng)前作用域s的值被釋放,棧里的值,傳遞給函數(shù),它對(duì)應(yīng)的操作是復(fù)制,例如,x=5傳遞給函數(shù)之后,在當(dāng)前作用域還是可以訪問(wèn)的。
1.6 返回值與作用域
返回值也可以轉(zhuǎn)移所有權(quán)。以下是一個(gè)示例:
fn main() {
let s1 = gives_ownership(); // gives_ownership 將返回值
// 轉(zhuǎn)移給 s1
let s2 = String::from("hello"); // s2 進(jìn)入作用域
let s3 = takes_and_gives_back(s2); // s2 被移動(dòng)到
// takes_and_gives_back 中,
// 它也將返回值移給 s3
} // 這里,s3 移出作用域并被丟棄。s2 也移出作用域,但已被移走,
// 所以什么也不會(huì)發(fā)生。s1 離開(kāi)作用域并被丟棄
fn gives_ownership() -> String { // gives_ownership 會(huì)將
// 返回值移動(dòng)給
// 調(diào)用它的函數(shù)
let some_string = String::from("yours"); // some_string 進(jìn)入作用域。
some_string // 返回 some_string
// 并移出給調(diào)用的函數(shù)
//
}
// takes_and_gives_back 將傳入字符串并返回該值
fn takes_and_gives_back(a_string: String) -> String { // a_string 進(jìn)入作用域
//
a_string // 返回 a_string 并移出給調(diào)用的函數(shù)
}
這里跟上面移動(dòng)規(guī)則一樣,將值賦給另一個(gè)變量時(shí)移動(dòng)它。當(dāng)持有堆中數(shù)據(jù)值的變量離開(kāi)作用域時(shí),其值將通過(guò)?
drop
?被清理掉,除非數(shù)據(jù)被移動(dòng)為另一個(gè)變量所有。
以上我們都是通過(guò)一個(gè)變量來(lái)獲取所有,這樣來(lái)回賦值就會(huì)有些啰嗦,這樣的話,我們可以使用元組來(lái)返回多個(gè)值。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-758934.html
fn main() {
let s1 = String::from("hello");
let (s2, len) = calculate_length(s1);
println!("The length of '{}' is {}.", s2, len);
}
fn calculate_length(s: String) -> (String, usize) {
let length = s.len(); // len() 返回字符串的長(zhǎng)度
(s, length)
}
但是這未免有些形式主義,而且這種場(chǎng)景應(yīng)該很常見(jiàn)。幸運(yùn)的是,Rust 對(duì)此提供了一個(gè)不用獲取所有權(quán)就可以使用值的功能,叫做?引用(references),后面我們?cè)賮?lái)看看引用是什么?文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-758934.html
到了這里,關(guān)于Rust核心功能之一(所有權(quán))的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!