所有権は、所有権のように錆理解が必要であり、メモリのガベージコレクションが存在しない場合に安全性を保証言語の錆ユニークな特徴です。ボロー、スライスやメモリ構造:次に、我々は、所有権とその機能のいくつかを議論します。
所有権とは何ですか
錆は、所有権のコア機能です。さまざまな言語では、メモリを管理する独自の方法を持っている、といくつかのメモリを管理するための独自のメカニズムを使用して手動のメモリ管理されているそのうちのいくつかは、ガベージコレクションを使用して、さびています。
所有権のルール
次のように所有権のルールは以下のとおりです。
- 錆の各値は、独自の変数を持っています。
- 変数の値をバインドすることができ、同時に錆。
- 変数がスコープから外れ、値が自動的に破棄されます。
後ろ向きスキップ、それは問題ではありません理解していません。
変数のスコープ
変数のスコープと錆言語他の言語では、例を見て似ています。
{ // 变量 s 还没有被声明,s 在这里是无效的
let s = "hello"; // 变量 s 是这里声明的,从这里开始生效
// 从这里开始,可以使用 s 做一些工作
} // 变量 s 超出作用域,s 从这里开始不再生效
これは、2つの重要な機能にまとめることができます。
- 変数の宣言は、発効した場合には
- 変数がスコープの障害のうちだとき
文字列型
その範囲の最後にスタックが、我々はそれを空にしたとき、それは錆に今あるメモリヒープ領域に格納されたデータを学ぶ際に第III章研究のデータ型は、スタックメモリ空間上の空きスペースに格納されています。
ここでは、もちろん、単にテキスト導入の具体的な内容を使用し、一例として、文字列型を使用します。
let s = "hello";
この例ではプログラムにハードコードされた文字列のハローで、我々はそれを呼び出す字符串文字
(私は本当に正しい言葉を考えることはできません、私は他の人が翻訳されているのか分からない文字列リテラル、最初ので、それを叫んだ)、文字列リテラル非常に便利な、しかし、それは次のような原則があり、例えば、我々はテキスト文字列を入力したい、任意のシーンには適用されません。
- 文字列リテラルを変更することはできません
- 不確実性のテキスト文字列の符号化(コンパイル)(例えば、ユーザの入力を保存するなど)コンテンツ
この場合は、我々は第二のタイプ--String文字列を使用します。これは、ヒープメモリに格納され、テキストの文字列から文字列を作成するために、関数からの使用を聞かせて、コンパイル時に文字列のサイズを知らないことができます。
let s = String::from("hello");
:最初は文字列に焦点を当てたと言うために、次の章のこの二重コロン構文の詳細は、例で作成した文字列は、次のような、変更することができます
let mut s = String::from("hello");
s.push_str(", world!"); // push_str() 在上个字符串后追加一个字符串
println!("{}", s); // 这里会打印 `hello, world!`
メモリの割り当て
テキストは直接、迅速かつ簡単に文字列リテラルの理由でプログラムにハードコード化することができるので、リテラルな内容は、コンパイル時に決定された文字列があります。文字列リテラルは不変である。しかし、我々は、未知の大きさや分布の変化のコンパイル時にメモリの文字列を与えることはできません。
文字列型のサポートの成長、さらには未知のサイズのコンパイル時に、ヒープメモリ割り当てスペース、手段でデータの格納にも使用することができ、文字列を変更します。
- (私たちは文字列を呼び出すとき::メソッドからメモリに対する要求を完了するために、同様に、ほとんどのプログラミング言語)メモリは、実行時にオペレーティングシステムからの要求でなければなりません
- このメモリを使用しない場合、我々は戻ってオペレーティングシステムにそれを取るでしょう(と他の言語、他の言語は、ガベージコレクションのメカニズムであるか、手動でメモリを解放)
錆は別の方法である:変数がスコープの外に出たら、メモリは自動的に戻り、次の例での概念を見てみましょう。
{
let s = String::from("hello"); // s 从这里开始生效
// 利用 s 做一些事情
} // s 超出作用域,不再生效
ドロップ機能、石の中括弧の右半分にするとき、プログラムの実行をメモリを返すために使用される}関数が呼び出されると、自動的に - sがスコープ外になると、錆が自動的に私たちは特別な関数を呼び出すのに役立ちます。
対話的変数とデータ:モバイル
錆に同一のデータのような変数を、複数の間の相互作用の異なる方法で使用することができます。
let x = 5; // 把 5 绑定到 x 上
let y = x; // 把 x 的值复制给 y,此时,x 和 y 的值都是 5
別の例:
let s1 = String::from("hello"); // 创建一个字符串绑定到 s1 上
let s2 = s1; // 把 s1 的值移动给 s2,此时,s1 就失效了,只有 s2 是有效的
文字列がヒープメモリに格納されているため、ここでデータを移動し、失敗なぜS1スタックメモリs1が浅いクローンと呼ばれる。このように同じヒープメモリは、またモバイル呼ばれ、S2にあります。あなたはs1がまだ有効である必要がある場合は、深いクローンを使用することができます。
変数とデータの相互作用:クローニング
深いクローンについて、我々は例を直接見:
let s1 = String::from("hello"); // 创建一个字符串绑定到 s1 上
let s2 = s1.clone(); // 把 s1 的值克隆给 s2,此时,s1 和 s2 都是有效的
println!("s1 = {}, s2 = {}", s1, s2); // 打印 s1 和 s2
次の章では、次に詳細にこの構文を説明しています。
唯一のスタックデータ:コピー
私たちは、前の例に戻ります:
let x = 5; // 把 5 绑定到 x 上
let y = x; // 把 x 的值复制给 y,此时,x 和 y 的值都是 5
println!("x = {}, y = {}", x, y); // 打印 x 和 y
そこにこのメソッドを使用して何のクローンはありませんが、xとyが有効です。xとyは整数であるので、整数でもcloneメソッドを呼び出すと、スタックメモリに保存されているが、また同じことを行います。コピーの種類を下記に示します:
- すべての整数、U32のように、I32
- ブール値のようなブール型は、値が真である、偽
- すべてのフロート、などのF32、F64
- 文字のような文字の種類、
- タプルは、だけ(U32、I32)のようなタプルの最初の4つの種類が含まれていますが、(U32、文字列)ではありません
所有権と機能
ここでの例では、次のように、はっきり言って直接配置する必要があります。
fn main() {
let s = String::from("hello"); // s 进入作用域
takes_ownership(s); // s 移动到函数里
// s 从这里开始不再生效,如果还使用 s,则会在编译期报错
let x = 5; // x 进入作用域
makes_copy(x); // x 复制(移动)到函数里,
// 但由于 x 是 i32 ,属于整型,仍有效
// 使用 x 做一些事情
} // x 超出作用域,由于 s 被移动到了函数中,这里不再释放 s 的内存
fn takes_ownership(some_string: String) { // some_string 进入作用域
println!("{}", some_string);
} // some_string 超出作用域,然后调用 drop 函数释放堆内存
fn makes_copy(some_integer: i32) { // some_integer 进入作用域
println!("{}", some_integer);
} // some_integer 超出作用域,但是整型不需要释放堆内存
戻り値と範囲
次のようにも直接、この例を置きます:
fn main() {
let s1 = gives_ownership(); // gives_ownership 把它的返回值移动给 s1
let s2 = String::from("hello"); // s2 进入作用域
let s3 = takes_and_gives_back(s2); // s2 移动进函数,函数返回值移动给 s3
} // s3 超出作用域被删除,s2 超出作用域被删除,s1 超出作用域被删除
fn gives_ownership() -> String { // gives_ownership 将移动它的返回值给调用者
let some_string = String::from("hello"); // some_string 进入作用域
some_string // some_string 是返回值,移出调用函数
}
// takes_and_gives_back 移入一个字符串,移出一个字符串
fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用域
a_string // a_string 是返回值,移出调用函数
}
参考資料とボロー
我々は言及を学ぶ機能内外に渡されたすべての値、目の前には、例を見て:
fn main() {
let s1 = String::from("hello"); // 创建一个字符串 s1
let len = calculate_length(&s1); // 把 字符串 s1 的引用传给函数
println!("The length of '{}' is {}.", s1, len); // 这里可以继续使用 s1
}
fn calculate_length(s: &String) -> usize { // 接收到 字符串 s1 的引用 s
s.len() // 这里返回函数的长度
} // s 超出作用域,但是这里没有字符串的所有权,不释放内存
&S1構文参照がスコープ外になったときにメモリ参照ポイント値を解放しない、基準値の点S1はなく、S1自体を作成することです。タイプパラメータも報知&機能するために必要と呼ばれる関数宣言パラメータ引数が参照で受信します。
変更の参照
あなたはcalculate_length関数の文字列を変更した場合、着信参照がデフォルトで不変に引用されているため、上記の例では、コンパイラは、参考文献の内容を変更したい場合は、あなたが見る、MUTのキーワードを追加する必要があり、文句を言うでしょう例:
fn main() {
let mut s = String::from("hello");
change(&mut s); // mut 同意函数修改 s 的值
}
fn change(some_string: &mut String) { // mut 声明函数需要修改 some_string 的值
some_string.push_str(", world"); // 追加字符串
}
変数参照は、一つの大きな制限があります。特に、同じスコープの参照のために、唯一の変数参照を有することができます。例えば:
let mut s = String::from("hello");
let r1 = &mut s; // 这是第一个借用的可变引用
let r2 = &mut s; // 这是第二个借用的可变引用,这里会编译不通过
println!("{}, {}", r1, r2);
この制限の利点は、コンパイラが停止競技中にデータをコンパイルすることができ、データ競合は、次の場合に発生しています。
- 同時に同じデータにアクセスするために2つ以上のポインタ
- 同じデータを書き込むことで複数のポインタ
- データを同期するためのメカニズムはありません
コンパイル時に錆がデータ競合状態を防止し、データ競争が予測できないエラーが発生しますし、ランタイムは、修復することが非常に困難です。しかし、異なるスコープに、等変数の参照、複数があります。
let mut s = String::from("hello");
{
let r1 = &mut s;
} // r1 超出作用域
// 这里可以借用新的可变引用了
let r2 = &mut s;
次のような変数の参照や参照不変一緒に、メイクのミス、
let mut s = String::from("hello");
let r1 = &s; // 不可变引用,没问题
let r2 = &s; // 不可变引用,没问题
let r3 = &mut s; // 可变引用,会发生大问题,因为后面还在使用 r1 和 r2
println!("{}, {}, and {}", r1, r2, r3);
参照されなくなりまし借りた不変の参照変数がある場合。不変の基準値を参照することにより修飾されていない、それは参照不変の複数から借用することができます。しかし、不変ではない参照が使用され、そして再びのような変数の参照を、借りることができる場合:
let mut s = String::from("hello");
let r1 = &s; // 不可变引用,没问题
let r2 = &s; // 不可变引用,没问题
println!("{} and {}", r1, r2);
// r1 和 r2 从这里开始不再使用了
let r3 = &mut s; // 可变引用,也没问题了,因为后面没使用 r1 和 r2 的了
println!("{}", r3);
宙ぶらりんの参照
いくつかのポインタの言語では、それが誤ってダングリングポインタを作成することは容易です。ポインタをダングリングも、このヒープポインタを指しながら、ヒープメモリは、解放されたメモリの早期放出へのポインタです。サビでは、コンパイラが無いダングリング参照することを保証することができます:あなたは任意の参照データを持っている場合、コンパイラはデータへの参照のような、スコープの外に出ないであろうことを保証します。
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String { // 这里希望返回字条串的引用
let s = String::from("hello"); // 创建字符串
&s // 这里返回了字符串的引用
} // 这里会把字符串的内存释放,因为 s 在这里超出作用域
この機能は、以下の例を見て、のような単純な変更になることです。
fn no_dangle() -> String { // 不返回字符串引用了,直接返回字符串
let s = String::from("hello");
s // 返回字符串
}
引用のルール
次のようにルールの参照は、以下のとおりです。
- 任意の時点で、1つまたはそれ以上の変数参照は不変引用しました
- リファレンスは、常に効果的でなければなりません
スライスタイプ
データの別の種類には、所有権のスライスではありません。スライスは、要素の隣接する一連のではなく、セット全体のコレクションです。
、関数があり、英語の文字列を入力し、最初の単語を返します。少しのプログラミングトピックを行います。スペースのない文字列は、文字列全体が単語とみなされた場合、それは文字列全体を返します。
のは、この関数の構造について考えてみましょう:
fn first_word(s: &String) -> ? // 这里应该返回什么
パラメータとして、この関数、文字列参照では、機能は全く所有権文字列を持っていない、私たちはそれを返す必要がありますか?私たちは、文字列の一部を返すことはできませんし、我々は、単語インデックスの第一の端部に戻ることができます。達成するために探します。
fn first_word(s: &String) -> usize { // 返回一个无符号整型,因为索引不会小于 0
let bytes = s.as_bytes(); // 把字符串转换化字节类型的数组
// iter 方法用于遍历字节数组,enumerate 方法用于返回一个元组,元组的第 0 个元素是索引,第 1 个元素是字节数组的元素
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' { // 如果找到了空格,就返回对应的索引
return i;
}
}
s.len() // 如果没找到空格,就返回字符串的长度
}
今、私たちはインデックスの終わりを見つける文字列の最初の単語を返しますが、問題があります:関数は符号なし整数を返し、戻り値は単なる意味のある数字文字列です。言い換えれば、戻り値と文字列値を分離して、我々はそれのような、意味を成さないことを保証することはできません。
fn main() {
let mut s = String::from("hello world"); // 创建字符串
let word = first_word(&s); // word 被赋值为 5
s.clear(); // 清空字符串,现在字符串的值是 ""
// word 仍是5,但是我们不能得到单词 hello 了,这里使用 word,编译器也不会报错,但是这真的是一个 bug
}
もう一つの問題は、我々は2番目の単語を取得したい場合、私はどのようにすればいいということでしょうか?関数の宣言は次のようになります。
fn second_word(s: &String) -> (usize, usize) {
あなたは言葉をたくさんしたいが、私はどのようにすればよいですか?
文字列のセクション
次のように文字列の文字列は、参照スライスの一部です。
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
それは、新しい変数値間隔左右の開閉とにを形成するために、文字列sに参加することで、あること、左、右のインデックスを含むインデックスが含まれていません。
左のインデックスが0である場合は、省略することができます。
let s = String::from("hello");
let slice = &s[0..2];
let slice = &s[..2]; // 和上一行等价
インデックスは、文字列の右端にある場合は、省略することができます。
let s = String::from("hello");
let len = s.len();
let slice = &s[3..len];
let slice = &s[3..]; // 和上一行等价
我々は文字列全体を取る場合は、両側を省略することができます。
let s = String::from("hello");
let len = s.len();
let slice = &s[0..len];
let slice = &s[..]; // 和上一行等价
私たちは今、それを行う方法を冒頭で述べたトピックを見て:
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
これは、直接、最初の単語の内容を返します。あなたが2番目の単語を返すようにしたい場合のように書くことができます。
fn second_word(s: &String) -> &str {
私たちは、問題を見ている、まだ空の文字列があります。
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s); // 不可变借用在这里声明
s.clear(); // 这里会报错,因为这里在修改 s 的内容
println!("the first word is: {}", word); // 不可变借用在这里使用
}
借り文と不変の間で借りた変数を使用することはできません。
文字列リテラルは、スライスされました
以前の私たちは、ストレージの問題の文字列リテラルについて話しました、そして今、我々はスライスを学び、私たちは、テキスト文字列を理解することができます。
let s = "Hello, world!";
Sタイプは&文字列本来不変である特殊なバイナリスライス位置へのポインタであり、&STR STRは、不変の参照であります
スライスされたパラメータとして文字列
フロントの文字列リテラルのスライスとスライス文字列型の学習、我々は、品質first_word機能を改善しなければなりません。これは&strの&文字列を引き起こすと同じ機能を使用することができますよう、経験豊富な開発者は、さび、引数の型&STRが書かれます。これは、直接の例で、理解しやすいようではありません。
fn main() {
let my_string = String::from("hello world"); // 创建字符串
// first_word 使用 &String切片
let word = first_word(&my_string[..]);
let my_string_literal = "hello world"; // 创建字符串
// first_word 使用 &str切片
let word = first_word(&my_string_literal[..]);
// 因为字符串文字已经是字符串切片了,可以不使用切片语法
let word = first_word(my_string_literal);
}
その他のセクション
私たちは、それだけの種類I32スライスの例を与えます:
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3]; // 值是: [2, 3]
独自の、ボローを使用して錆、スライス、コンパイル時に安全プログラムのメモリ確保。さび言語は、手動でメモリを管理するために所有者が自動的にクリーンアップされる範囲のデータを外出時に私たちは、余分なコードを記述する必要なく、メモリを制御するために、他のプログラミング言語と同じ方法を提供します。
ようこそ、単一の研究では、鵬飛ノート