專欄簡(jiǎn)介:本專欄作為Rust語(yǔ)言的入門級(jí)的文章,目的是為了分享關(guān)于Rust語(yǔ)言的編程技巧和知識(shí)。對(duì)于Rust語(yǔ)言,雖然歷史沒有C++、和python歷史悠遠(yuǎn),但是它的優(yōu)點(diǎn)可以說是非常的多,既繼承了C++運(yùn)行速度,還擁有了Java的內(nèi)存管理,就我個(gè)人來(lái)說,還有一個(gè)優(yōu)點(diǎn)就是集成化的編譯工具cargo,語(yǔ)句風(fēng)格和C++極其相似,所以說我本人還是比較喜歡這個(gè)語(yǔ)言,特此建立這個(gè)專欄,作為學(xué)習(xí)的記錄分享。
日常分享:每天努力一點(diǎn),不為別的,只是為了日后,能夠多一些選擇,選擇舒心的日子,選擇自己喜歡的人!
目錄
Rust獨(dú)有的所有權(quán)
所有權(quán)
棧
堆
所有權(quán)的規(guī)則
String類型
內(nèi)存與分配
?變量數(shù)據(jù)的移動(dòng)
字面值
String類型
變量數(shù)據(jù)的克隆與拷貝
克隆
棧上數(shù)據(jù)的拷貝
所有權(quán)與函數(shù)
返回值與作用域
引用與借用
可變引用
懸垂引用
引用的規(guī)則
Slice類型
字符串字面值是slice
?字符串slice作為參數(shù)
其他類型的slice
?總結(jié)
Rust獨(dú)有的所有權(quán)
所有權(quán)這個(gè)特性時(shí)Rust獨(dú)有的,前面第一章我們說了,Rust語(yǔ)言集合了C++,java的優(yōu)點(diǎn),而為了解決C++中垃圾無(wú)法回收,容易造成內(nèi)存泄漏的特點(diǎn),Rust中提出了所有權(quán)這個(gè)概念。所以說,認(rèn)識(shí)所有權(quán)才是掌握Rust的必不可少的部分。
所有權(quán)
Rust的核心功能之一是所有權(quán)。下面我們來(lái)認(rèn)識(shí)認(rèn)識(shí)所有權(quán)。
學(xué)習(xí)過c++和java的人應(yīng)該知道,在這兩種語(yǔ)言中,c++需要開發(fā)者自己分配和釋放內(nèi)存,這種情況下很多開發(fā)者會(huì)在使用了內(nèi)存而不釋放,導(dǎo)致內(nèi)存泄漏。而Java則是解決了這種問題,他采用的是垃圾回收機(jī)制,在程序運(yùn)行的時(shí)候自動(dòng)的尋找不再使用的內(nèi)存。而Rust使用的則是所有權(quán)管理,簡(jiǎn)單的來(lái)說,就是在程序編譯時(shí)就會(huì)進(jìn)行規(guī)格檢查,提前檢測(cè)出可能會(huì)存在內(nèi)存泄漏等問題。其實(shí)這個(gè)有點(diǎn)和微軟公司下的visual studio編譯器很相似,對(duì)內(nèi)存管理很嚴(yán)格。
棧
提到棧,很多人應(yīng)該能想到數(shù)據(jù)結(jié)構(gòu)中的棧,棧的特點(diǎn)就是“先進(jìn)后出”,棧的內(nèi)存是連續(xù)的,存放數(shù)據(jù)總是按照順序方式存入,而在棧中,存入的數(shù)據(jù)必須是大小固定的,且已經(jīng)知道的,在C++中,在開辟內(nèi)存空間的時(shí)候,一般都是在棧上開辟的,所以必須要指明數(shù)據(jù)類型,以此來(lái)告訴系統(tǒng)開辟的空間是固定且已知大小的。
堆
我們定義的指針則是在堆區(qū),因?yàn)槲覀兪遣恢谰唧w的內(nèi)存大小的,只能分配一個(gè)足夠大的內(nèi)存空間。。 堆是缺乏組織的:當(dāng)向堆放入數(shù)據(jù)時(shí),你要請(qǐng)求一定大小的空間。內(nèi)存分配器(memory allocator)在堆的某處找到一塊足夠大的空位,把它標(biāo)記為已使用,并返回一個(gè)表示該位置地址的 指針(pointer)。
所以說,當(dāng)我們定義數(shù)據(jù)的內(nèi)存大小不會(huì)變的時(shí)候就在棧區(qū)開辟空間,如果不確定會(huì)不會(huì)改變數(shù)據(jù)的大小,則在堆區(qū)開辟空間。
所有權(quán)的規(guī)則
- Rust 中的每一個(gè)值都有一個(gè) 所有者(owner)。
- 值在任一時(shí)刻有且只有一個(gè)所有者。
- 當(dāng)所有者(變量)離開作用域,這個(gè)值將被丟棄。
記住,和其他語(yǔ)言一樣,變量(所有者)值只在對(duì)應(yīng)的作用域起作用,超出作用域,則無(wú)法使用。
String類型
這里我們將String類型提出來(lái)單獨(dú)講解,其實(shí)是因?yàn)镾tring類型有點(diǎn)獨(dú)特,它是可變的,不再是固定的,所以說,將String類單獨(dú)提出來(lái)講解,而不是歸于字面值。String類型的數(shù)據(jù)被分配在堆區(qū),所以大小可以隨時(shí)變化。
fn main()
{
let mut s=String::from("Hello"); //從字面值中獲取字符串
println!("{}", s);
s.push_str(",World"); //在字符串s后追加字符串
println!("{}",s);
}
對(duì)于String類型中的一些函數(shù), 大家可以去官網(wǎng)查看,這里就不過多介紹,至于String類型中函數(shù)的調(diào)用方法,會(huì)在后面將到。
內(nèi)存與分配
前面說了,String類型是在堆區(qū)上分配內(nèi)存的,最開始系統(tǒng)并不知道你需要多大的內(nèi)存,就分配一個(gè)很大的內(nèi)存空間,
- 必須在運(yùn)行時(shí)向內(nèi)存分配器(memory allocator)請(qǐng)求內(nèi)存。
- 需要一個(gè)當(dāng)我們處理完
String
時(shí)將內(nèi)存返回給分配器的方法。
第一個(gè)部分是我們完成,在獲取字面值的時(shí)候就請(qǐng)求分配內(nèi)存,這在任何一門語(yǔ)言中均適用。
第二部分就需要根據(jù)不同語(yǔ)言的特性來(lái)進(jìn)行操作了。比如說Java有垃圾回收機(jī)制,他會(huì)記錄并清除不再使用的內(nèi)存,我們不需要太多的關(guān)心內(nèi)存。然而,像C++,Python這種沒有垃圾回收機(jī)制的語(yǔ)言,需要自己去釋放,比如說,C++中的析溝函數(shù),會(huì)在類創(chuàng)建并使用完畢后自動(dòng)釋放內(nèi)存,這是由于析溝函數(shù)自動(dòng)調(diào)用了drop()函數(shù)。對(duì)于一些變量,我們也需要人為的去釋放,使用drop()或者delete()函數(shù)。
但是在Rust中,當(dāng)變量離開他所在的作用域的時(shí)候就會(huì)自動(dòng)釋放。Rust自動(dòng)調(diào)用了特殊的函數(shù),drop()函數(shù),也可以自己手動(dòng)調(diào)用。
fn main()
{
let mut s=String::from("Hello"); //從字面值中獲取字符串
println!("{}", s);
s.push_str(",World"); //在字符串s后追加字符串
println!("{}",s);
drop(s); //釋放掉內(nèi)存,所有者s不再存在
}
?變量數(shù)據(jù)的移動(dòng)
字面值
fn main()
{
let s1=2;
let s2=s1;
println!("{}", s1);
println!("{}", s2);
}
如上述例子,先定義了一個(gè)變量s1,然后綁定到數(shù)值5上,再定義一個(gè)變量s2綁定到s1上,也就是說,兩個(gè)變量的值都是5.由于他們?cè)诙x的時(shí)候就已經(jīng)指明了數(shù)值大小,所以這兩個(gè)變量存放在棧區(qū),所以是按照順序存放。兩個(gè)變量都有效。那如果是存放在堆區(qū)的又該如何?
String類型
fn main()
{
let s1=String::from("Hello!");
let s2=s1;
println!("{}", s1);
println!("{}", s2);
}
上述例子,運(yùn)行你會(huì)發(fā)現(xiàn)報(bào)錯(cuò)了,顯示s1值不存在,這是為什么?我們先來(lái)介紹一下String類型的數(shù)據(jù)的概念,然后在來(lái)解釋這個(gè)問題。
我們以s1為例:
?
?
string類型的數(shù)據(jù)由三部分組成: 一個(gè)指向存放字符串內(nèi)容內(nèi)存的指針,一個(gè)長(zhǎng)度,和一個(gè)容量。這一組數(shù)據(jù)存儲(chǔ)在棧上。右側(cè)則是堆上存放內(nèi)容的內(nèi)存部分。
長(zhǎng)度表示 String
的內(nèi)容當(dāng)前使用了多少字節(jié)的內(nèi)存。容量是 String
從分配器總共獲取了多少字節(jié)的內(nèi)存。
當(dāng)我們將s2綁定到s1上的時(shí)候:
圖示
也就是說,只拷貝了棧上的數(shù)據(jù),而堆上的數(shù)據(jù)則沒有被拷貝,兩個(gè)變量共同指向堆區(qū)數(shù)值。 回到上面的問題,為什么s1會(huì)不存在,這是由于,如果s1存在,那么將有兩個(gè)變量同時(shí)擁有同一個(gè)字面值,在離開作用域時(shí),系統(tǒng)會(huì)自動(dòng)調(diào)用drop()函數(shù),這時(shí)就會(huì)出現(xiàn)兩個(gè)變量都會(huì)被釋放,就出現(xiàn)了二次釋放(double free)的錯(cuò)誤。這是不被允許的。所以Rust在s2綁定到s1上時(shí),就將s1清除。從而不能再使用。
?Compiling number v0.1.0 (/home/ddb/文檔/number)
error[E0382]: borrow of moved value: `s1`
?--> src/main.rs:5:18
? |
3 |?? let s1=String::from("Hello!");
? |?????? -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
4 |?? let s2=s1;
? |????????? -- value moved here
5 |?? println!("{}", s1);
? |????????????????? ^^ value borrowed here after move
? |
? = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)For more information about this error, try `rustc --explain E0382`.
error: could not compile `number` due to previous error
變量數(shù)據(jù)的克隆與拷貝
克隆
和其他語(yǔ)言一樣,Rust也提供了克隆的方法??梢岳斫鉃樯羁截悾瑢⒍褏^(qū)的數(shù)據(jù)也拷貝一份。
fn main()
{
let s1=String::from("Hello!");
let s2=s1.clone();
println!("{}", s1);
println!("{}", s2);
}
棧上數(shù)據(jù)的拷貝
fn main()
{
let s1=10;
let s2=s1;
println!("{}", s1);
println!("{}", s2);
}
這里的一個(gè)例子,兩個(gè)變量都是在棧上,所以他們的數(shù)據(jù)之間進(jìn)行的是拷貝,兩個(gè)變量都可以存在。可以實(shí)現(xiàn)copy的數(shù)據(jù)類型有:
- 所有整數(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)
就沒有。
所有權(quán)與函數(shù)
將值傳遞給函數(shù)與給變量賦值的時(shí)候原理極為相似。所以,向函數(shù)傳遞值可能會(huì)移動(dòng)或者復(fù)制。
fn main()
{
let s=String::from("Hello, world!"); //s進(jìn)入作用域
String_move(s); //s的值移動(dòng)到函數(shù)里,后面s不再有效
let x=10;//x進(jìn)入作用域
number_copy(x); //x進(jìn)入函數(shù)里,但是x是i32的,所以是復(fù)制進(jìn)來(lái),后面繼續(xù)有效.
println!("{}", x);
}
fn String_move(something: String) //something進(jìn)入作用域
{
println!("{}",something);
} //離開作用域,調(diào)用drop函數(shù),something失效。
fn number_copy(number:i32) //number進(jìn)入作用域,開始起作用
{
println!("{}",number);
}//移出作用域
返回值與作用域
fn main()
{
let s1=givs_ownership();
let s2=String::from("test");
let s3=takes_and_gives_back_ownership(s2); //s2被移動(dòng)到函數(shù)中,返回值給s3,不再起作用.
} //s1,s2,s3均離開作用域,不起作用,但s2已被移走,不會(huì)發(fā)生什么。
fn givs_ownership()->String
{
let something=String::from("something"); // "something"進(jìn)入作用域
return something;//返回"something",并移出給調(diào)用的函數(shù)
}
fn takes_and_gives_back_ownership(a_String:String)->String
{
a_String; //返回a_String,并移出給調(diào)用函數(shù)。
}
上面兩個(gè)例子都是在說明所有權(quán)問題,一個(gè)值只能有一個(gè)所有者,所以,上面不同的函數(shù),不同的數(shù)據(jù)類型之間使用的是不同的方法,有的是移動(dòng),有的是克隆。在這一點(diǎn)上,一定要注意區(qū)分。
注意,函數(shù)在返回值上面,可以返回多個(gè)值,但是是以元組的方式返回。
fn main()
{
let s1=String::from("hello world");
let (num,s2)=string_length(s1);
println!("{} {}",num,s2);
}
fn string_length(s:String)->(usize,String)
{
let length=s.len();
return (length,s);
}
引用與借用
引用(reference)像一個(gè)指針,因?yàn)樗且粋€(gè)地址,我們可以由此訪問儲(chǔ)存于該地址的屬于其他變量的數(shù)據(jù)。 與指針不同,引用確保指向某個(gè)特定類型的有效值。
fn main()
{
let s=String::from("hello");
let length:usize =string_length(&s);
println!("the length of the string s is: {}",length);
}
fn string_length(s: &String) -> usize
{
return s.len();
}
?上面函數(shù)中“&s”表示的是建立一個(gè)引用,指向s的數(shù)據(jù)值,但是并不擁有它,也就不會(huì)有所有權(quán)這個(gè)東西。所以在離開作用域后對(duì)原本的數(shù)據(jù)值沒什么影響。
在Rust中,我們把創(chuàng)建一個(gè)引用的行為稱為借用。
可變引用
fn main()
{
let mut s=String::from("hello");
let length:String =string_length(&mut s);
println!("{}",s);
println!("position is {}",length);
}
fn string_length(s: &mut String) ->String
{
s.push_str(",world!");
let m:String = String::from("Successfull!");
return m;
}
上面的代碼就是一個(gè)可變引用的實(shí)現(xiàn),我們可以看到,可變引用就是在引用前加一個(gè)mut關(guān)鍵字。
上面我們說過,引用只是暫時(shí)借用數(shù)據(jù),并不擁有所有權(quán),所以,一個(gè)變量被創(chuàng)建了可變引用的時(shí)候,他只能被創(chuàng)建一次,否則會(huì)報(bào)錯(cuò),這是由于,當(dāng)你創(chuàng)建了多個(gè)可變引用的時(shí)候,他們都可以更改原本的數(shù)據(jù),這個(gè)時(shí)候系統(tǒng)就不會(huì)知道那一個(gè)改變?cè)谇懊妫且粋€(gè)在后面。就會(huì)出現(xiàn)混亂。
例如:
ddb@ddb-NBLK-WAX9X:~/文檔/number$ cargo run
?? Compiling number v0.1.0 (/home/ddb/文檔/number)
error[E0499]: cannot borrow `sln` as mutable more than once at a time
?--> src/main.rs:5:10
? |
4 |?? let m1=&mut sln;
? |????????? -------- first mutable borrow occurs here
5 |?? let m2=&mut sln;
? |????????? ^^^^^^^^ second mutable borrow occurs here
6 |?? println!("{},{}",m1,m2);
? |??????????????????? -- first borrow later used hereFor more information about this error, try `rustc --explain E0499`.
error: could not compile `number` due to previous error
?出現(xiàn)上面這種情況,在官方的說法中是數(shù)據(jù)競(jìng)爭(zhēng)。導(dǎo)致這種情況的原因:
- 兩個(gè)或更多指針同時(shí)訪問同一數(shù)據(jù)。
- 至少有一個(gè)指針被用來(lái)寫入數(shù)據(jù)。
- 沒有同步數(shù)據(jù)訪問的機(jī)制。
?除了不能同時(shí)擁有多個(gè)可變引用,還不能同時(shí)存在可變與不可變引用。舉個(gè)簡(jiǎn)單的例子,你有一個(gè)玩具,有人來(lái)借,然后會(huì)原樣還回,但是有人卻改變了他的模樣,你還回去的玩具和他的不一樣,是不是就會(huì)出現(xiàn)矛盾。
let mut sln=String::from("I like Rust!");
let m1=&sln;
let m2=&sln;
let m3=&mut sln;
println!("{},{},{}",m1,m2,m3);
像上面這種情況就會(huì)出現(xiàn)報(bào)錯(cuò)。
懸垂引用
在具有指針的語(yǔ)言中,很容易通過釋放內(nèi)存時(shí)保留指向它的指針而錯(cuò)誤地生成一個(gè) 懸垂指針(dangling pointer),所謂懸垂指針是其指向的內(nèi)存可能已經(jīng)被分配給其它持有者。相比之下,在 Rust 中編譯器確保引用永遠(yuǎn)也不會(huì)變成懸垂?fàn)顟B(tài):當(dāng)你擁有一些數(shù)據(jù)的引用,編譯器確保數(shù)據(jù)不會(huì)在其引用之前離開作用域。
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}
這里出現(xiàn)了報(bào)錯(cuò),這是由于s在離開作用域后就失去了作用,不再有任何的作用,所以引用肯定也沒作用了。
引用的規(guī)則
- 在任意給定時(shí)間,要么 只能有一個(gè)可變引用,要么 只能有多個(gè)不可變引用。
- 引用必須總是有效的。
Slice類型
slice 允許你引用集合中一段連續(xù)的元素序列,而不用引用整個(gè)集合。slice 是一類引用,所以它沒有所有權(quán)。
fn main()
{
let mut s=String::from("hello world");
let world=first_world(&s);
println!("{}", world);
s.clear(); //清空字符串
}
fn first_world(s: &String) -> usize {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return i;
}
}
s.len()
}
看上面,上面的這段代碼表示的是找到空格的所在位置,對(duì)于這個(gè)函數(shù),使用了很多的庫(kù)函數(shù)才求出索引值,那么有沒有簡(jiǎn)單方法?
這里我們必須提到字符串slice,在Python中,我們可以直接使用索引值求的字符串中的部分字符串,而在Rust中,也有這種機(jī)制,
fn main() {
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
println!("{},{}", world, hello);
}
和Python一樣,他的索引方式也有很多,比如s[..3],s[..],s[2..]他們分別表示的是從0下標(biāo)開始到3位置,從0開始到尾部結(jié)束,從2開始到結(jié)束。
字符串字面值是slice
前面我們說過,字符串字面值和String的區(qū)別,字符串字面值是不可變的,這是由于字符串字面值的數(shù)據(jù)類型就是&str。
例如:
let s = "Hello, world!";
這里 s
的類型是 &str
:它是一個(gè)指向二進(jìn)制程序特定位置的 slice。這也就是為什么字符串字面值是不可變的;&str
是一個(gè)不可變引用。
?字符串slice作為參數(shù)
slice也可以作為函數(shù)的返回?cái)?shù)據(jù)類型和參數(shù)類型:
fn main() { let s=String::from("Hello,world"); let mun=&s[..]; let a=Slice_from(mun); println!("{}",a); } fn Slice_from(s:&str)->&str { let sl:String = String::from("Hello, world"); let world = &sl[6..11]; let hello=&s[0..5]; println!("{}", world); return hello; }
其他類型的slice
字符串 slice,正如你想象的那樣,是針對(duì)字符串的。不過也有更通用的 slice 類型。考慮一下這個(gè)數(shù)組:
let a = [1, 2, 3, 4, 5];
就跟我們想要獲取字符串的一部分那樣,我們也會(huì)想要引用數(shù)組的一部分。我們可以這樣做:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-633407.html
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
assert_eq!(slice, &[2, 3]);
?總結(jié)
本節(jié)內(nèi)容比較多,主體意思就是說在Rust中,Rust 語(yǔ)言提供了跟其他系統(tǒng)編程語(yǔ)言相同的方式來(lái)控制你使用的內(nèi)存,但擁有數(shù)據(jù)所有者在離開作用域后自動(dòng)清除其數(shù)據(jù)的功能意味著你無(wú)須額外編寫和調(diào)試相關(guān)的控制代碼。所以說,只要知道Rust語(yǔ)言的特有機(jī)制,學(xué)起來(lái)就會(huì)簡(jiǎn)單很多。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-633407.html
到了這里,關(guān)于認(rèn)識(shí)所有權(quán)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!