Understanding Ownership in Rust

Let's Get Rusty
7 Feb 202125:30

Summary

TLDRこの動画はRustプログラミング言語についての「Let's Get Rusty」チャンネルのボグダンが説明しています。特にRustの独自の特徴である所有権モデルについて深く掘り下げています。所有権はRustがガベージコレクターを使わずにメモリ安全性を保証する仕組みです。動画では、参照、借用、スライス型、データのメモリ配置などについても触れ、Rustのメモリ管理の厳格なルールとトレードオフについても議論されています。

Takeaways

  • 📚 Rust言語の特徴は所有権モデルによるメモリ管理です。
  • 🗑️ ガベージコレクターを使用しないでメモリの安全性を保証できます。
  • 🔄 所有権モデルは、メモリ管理のエラーをコンパイル時にチェックします。
  • 📈 所有権の3つのルール:1つの値は1つの所有者、同じタイミングで所有者は1人、所有者がスコープ外になると値はドロップされます。
  • 🔄 変数間のデータのやりとりには、ムーブとクローンの2つの方法があります。
  • 🔗 参照は所有権を取得せず、関数に渡す際に値を借用します。
  • 🔄 関数から所有権を返す際は、所有権を取得して返却するか、借用して返却することができます。
  • 🔗 参照には不変参照と可変参照があり、可変参照は1つのスコープ内で1つだけ存在できます。
  • 🔍 ダングリング参照は、無効なデータを指す参照であり、Rustはこれを防止します。
  • 🔍 スライスは、コレクション内の連続した要素の参照を提供し、所有権を取得しません。
  • 📈 スライスは、文字列や配列などの複数のデータ型で使用でき、範囲を指定することができます。

Q & A

  • Rustの所有権モデルとは何ですか?

    -Rustの所有権モデルは、メモリを管理する方法で、ガベージコレクターを使わずにメモリの安全性を保証する機能です。

  • ガベージコレクターとマニュアルメモリ管理の利点と欠点は何ですか?

    -ガベージコレクターの利点は、エラーが発生しにくいと実行時に高速でプログラムを書くことができます。欠点は、メモリの細かい制御ができず、実行時パフォーマンスが遅くなる可能性があり、プログラムのサイズが大きくなるです。マニュアルメモリ管理の利点は、完全なメモリ制御とより高速な実行時とより小さなプログラムサイズです。欠点は、メモリ管理が非常にエラーが発生しやすく、プログラムを書く時間が長くなることです。

  • Rustの所有権モデルはどのようにメモリの安全性を保証しますか?

    -Rustは、コンパイル時に多くのチェックを行い、メモリの使用が安全であることを確認することで、メモリの安全性を保証しています。

  • スタックとヒープの違いは何ですか?

    -スタックは固定サイズで実行時に変更されず、関数の実行に必要なローカル変数を格納します。ヒープは、実行時に成長または縮小し、動的なサイズのデータを格納することができます。

  • Rustにおける所有権の3つのルールは何ですか?

    -1. Rustの各値には所有者が存在します。2. 一度に所有者を複数持つことはできません。3. 所有者がスコープから抜けると、値はドロップされます。

  • Rustで値をクローンするにはどうすればいいですか?

    -Rustで値をクローンするには、`.clone()`メソッドを使用します。これはより高価な操作であり、所有権を移動するのではなく、値のコピーを作成します。

  • 関数に値を渡すとき、Rustはどのように所有権を処理しますか?

    -関数に値を渡すと、Rustはその値を関数の引数に移動します。これは、値が関数の引数に割り当てられる際に発生し、所有権が引数に移されます。

  • 参照とは何ですか?

    -参照は、値を所有する代わりに、その値を借用するものです。参照は、所有権を移動せずに値にアクセスし、変更することができます。

  • Rustにおけるスライスとは何ですか?

    -スライスは、コレクション内の連続した要素のシーケンスを参照するものです。これにより、コレクション全体を所有せずに特定の部分にアクセスすることができます。

  • Rustでダングリング参照を防ぐためにどうすればいいですか?

    -Rustは、ダングリング参照を防止するために、関数の戻り値が借用値を含む場合、その値が存在する限り参照を有効に保つようにコンパイラに指示します。

  • Rustの所有権、参照、およびスライスについて学んだことで、どのようにプログラムをより安全に記述できますか?

    -所有権、参照、およびスライスの概念を理解することで、Rustを使ってプログラムを書く際に、メモリの安全性を確保しながら効率的なメモリ管理を行うことができます。これにより、ランタイムのメモリの問題を回避し、プログラムの信頼性を高めることができます。

Outlines

00:00

📚 はじめに

この動画はRustプログラミング言語についてのチャンネル「let's get rusty」です。前回は「Rustling Book」の第3章で基本的なプログラミング概念をRustの文脈で学びましたが、今回は第4章に焦点を当て、Rustのユニークな所有権モデルについて学びます。所有権はメモリ安全性をガベージコレクターなしで保証する機能です。

05:02

🧠 所有権モデルとメモリ管理

Rustの所有権モデルはメモリ管理の一種で、ガベージコレクターと手動メモリ管理の対極的なアプローチを提供します。ガベージコレクターはエラーが発生しないが、プログラムのサイズが大きくなり、ランタイムパフォーマンスが不安定になります。一方、手動メモリ管理は完全な制御を提供しますが、エラーが発生しやすく、プログラムの書き込みが遅くなります。Rustはこれらのトレードオフを解決し、所有権モデルを導入しました。

10:02

📌 所有権の基本

Rustにおける所有権には3つの基本的なルールがあります。1つは、各値が1つの所有者を持つこと。2つ目は、同時に複数の所有者が存在しないこと。3つ目は、所有者がスコープを退出すると、値はドロップされます。これにより、Rustはメモリ安全性を確保しますが、代償としてコンパイル時のチェックが厳格になり、初期の学習曲線が険しくなります。

15:03

🔄 関数と所有権

関数に値を渡す際、Rustでは値の所有権が関数に移されます。これは、関数が値を消費する場合や、値を返す場合に重要です。関数から値を返す際には、所有権が呼び出し元に戻る必要があります。参照とムーブの概念がここで重要であり、参照は所有権を取得せずに値を借用する方法です。

20:04

🔗 参照と借用

参照は、値を所有する代わりに借用する方法です。参照は、所有権を取得せずに値にアクセスできるようにします。Rustでは、参照は不変参照と可変参照の2種類があり、それぞれのスコープ内で1つの可変参照しか存在できないという制限があります。これにより、データ競合をコンパイル時に防ぐことができます。

25:06

📈 スライスとデータ参照

スライスは、コレクション内の連続した要素のシーケンスを参照する方法です。スライスは所有権を取得せず、関数に渡す際に便利です。スライスは、文字列や配列などの複数のデータ型に適用できます。スライスは、関数から返された値が変わる場合でも、元のデータに安全にアクセスできるようにします。

🎉 動画の締めくくり

この動画では、Rustの所有権、メモリ管理、参照、借用、スライス型について学びました。これらの概念はRustの安全性とパフォーマンスを理解する上で重要です。この動画が役に立てば、いいねをクリックし、チャンネルを購読して次の動画が通知されるようにしてください。

Mindmap

Keywords

💡Rust

Rustは、システムプログラミング言語で、メモリの安全性を確保しながら、ガベージコレクターを使わない方法でメモリ管理を行う言語です。このビデオでは、Rustの所有権モデルとその特徴について説明されています。

💡所有権モデル (Ownership Model)

所有権モデルは、Rustにおけるメモリ管理の方法であり、値が所有者によって管理されることを意味します。このモデルは、Rustがメモリ安全性を提供する关键的な機能です。

💡参照 (References)

参照は、Rustでの借用の概念であり、値を所有するのではなく、その値を参照するためのポインタです。参照は、所有権を取得せずに値を関数に渡す方法として使用されます。

💡借用 (Borrowing)

借用は、Rustにおける参照を使用する際のルールのことを指します。借用は、関数内で値を変更したり、値を所有するように見せかけたりすることなく、値を一時的に使用する場合に使用されます。

💡スライス (Slices)

スライスは、コレクションの連続した要素の参照を提供します。スライスは、所有権を取得せずに、コレクションの一部にアクセスする方法です。

💡スタック (Stack)

スタックは、プログラム実行時に使用される固定サイズのメモリ領域で、関数のローカル変数を格納するために使用されます。

💡ヒープ (Heap)

ヒープは、プログラム実行時に動的にサイズが変更できるメモリ領域で、大きなデータ構造やオブジェクトを格納するために使用されます。

💡ドロップ (Drop)

ドロップは、Rustにおける値の破棄を意味します。所有者が範囲外になると、その値はドロップされ、メモリが解放されます。

💡ムーブ (Move)

ムーブは、Rustにおける値の所有権の移転を意味します。値が関数に渡されると、その値はムーブされ、元の所有者にはアクセスできなくなります。

💡クローン (Clone)

クローンは、Rustにおける値の複製を意味します。クローンメソッドを使用することで、値のコピーを生成できます。

💡データレース (Data Race)

データレースは、2つ以上のポインタが同じデータに指し、かつ1つがデータを書き込む際に、データアクセスを同期するためのメカニズムがない場合に起こる現象です。これにより、データが破壊される可能性があります。

Highlights

Rust programming language channel introduction

Discussion of Rust's unique ownership model for memory management

Comparison of garbage collection and manual memory management

Explanation of the trade-offs between memory safety and runtime performance

Introduction to the stack and heap memory allocation

Description of the three ownership rules in Rust

Example of moving vs. copying in Rust

Explanation of the `clone` method in Rust

Discussion of ownership and functions in Rust

Introduction to references and borrowing in Rust

Explanation of mutable and immutable references

Prevention of data races through Rust's reference rules

Definition and use of slices in Rust

Demonstration of string slices and their advantages

Modification of the 'first word' function using string slices

Comparison of string slices with string literals

Application of slices on different types of collections

Summary of Chapter 4 of the Rust book

Transcripts

play00:00

welcome back to let's get rusty my name

play00:02

is bogdan and this channel is

play00:04

all about the rust programming language

play00:06

if that sounds interesting to you

play00:07

hover over that subscribe button and

play00:09

give it a fist bump last time we went

play00:11

over chapter 3

play00:12

of the rustling book in which we covered

play00:14

basic programming concepts in the

play00:16

context of rust

play00:17

if you haven't already check that video

play00:19

out in this video we're going to be

play00:20

talking about chapter four and this is a

play00:22

very special video because chapter four

play00:24

covers rust's most

play00:25

unique feature ownership ownership is

play00:28

what allows for russ to make memory

play00:29

safety guarantees without the use of a

play00:32

garbage collector we'll also cover

play00:33

references

play00:34

borrowing the slice type and how rust

play00:37

lays out data

play00:38

in memory so with that let's get started

play00:40

first we'll answer the basic question

play00:42

what is ownership or the ownership model

play00:44

in rust the ownership model is a way to

play00:46

manage memory

play00:47

now you might ask why do we need a way

play00:49

to manage memory and to understand it's

play00:51

helpful to look at the two other

play00:53

solutions for managing memory today

play00:55

first we have garbage collection

play00:57

if you've ever written an app using a

play00:58

higher level programming language

play01:00

such as java or c-sharp you didn't have

play01:02

to worry about

play01:03

memory management because the garbage

play01:05

collector did it for you now this

play01:07

approach has some pros and cons the

play01:08

first

play01:09

pro is that it's error-free meaning that

play01:11

if you were to manage memory

play01:13

yourself you might introduce memory bugs

play01:15

but since that's being handled by the

play01:17

garbage collector you can be pretty sure

play01:18

that there aren't any

play01:19

memory issues i put asterisks because

play01:21

garbage collectors could have bugs

play01:23

themselves

play01:24

but for the most part you can be rest

play01:25

assured that your memory is being

play01:27

managed safely the second pro

play01:29

is faster right time because you don't

play01:31

have to deal with memory

play01:32

you can write your programs faster now

play01:34

let's look at the cons

play01:35

firstly we're giving up fine grain

play01:37

control of our memory that's because the

play01:39

garbage

play01:40

collector now handles all our memory

play01:41

secondly our runtime performance can be

play01:44

slower and more unpredictable

play01:46

slower because we can't manually

play01:48

optimize our memory and unpredictable

play01:50

because the garbage collector could

play01:51

choose to clean up memory at any point

play01:53

in time and when it does so it slows

play01:55

down our program

play01:56

lastly we have a larger program size

play01:58

because the garbage collector

play02:00

is a piece of code that we have to

play02:01

include within our program

play02:03

now let's look at manual memory

play02:05

management if you've written c

play02:06

or c plus you have to allocate and

play02:08

deallocate memory

play02:10

manually now the pros of this is full

play02:12

control over your memory which leads to

play02:14

a faster runtime because you can

play02:16

optimize things and a smaller program

play02:18

size because you don't have to include

play02:19

a garbage collector the cons are first

play02:22

it's extremely

play02:23

error prone many many bugs and security

play02:25

issues are caused by incorrect memory

play02:28

management

play02:28

and secondly you have a slower right

play02:30

time because you have to think about

play02:32

memory it takes longer to write your

play02:34

program notice here that the pros and

play02:36

cons of garbage collection and the pros

play02:38

and cons of manual memory management are

play02:40

the exact

play02:41

opposite we're making opposite

play02:42

trade-offs here either of these

play02:44

solutions could be appropriate

play02:45

depending on the context if you're

play02:47

writing a high-level app like a website

play02:49

it makes sense to sacrifice runtime

play02:51

performance and have a larger program

play02:53

size

play02:53

for the ease of use and faster right

play02:55

time you get with a garbage collector

play02:57

on the other hand if you're writing low

play02:59

level system components then you

play03:01

probably care a lot more

play03:02

about runtime performance and program

play03:04

size so it would make more sense to use

play03:06

manual memory management now we could

play03:08

talk about the ownership model

play03:09

which is a third way to manage memory

play03:11

now rust is a systems

play03:13

programming language so it does care

play03:15

about runtime performance

play03:17

and program size and as you can see here

play03:19

we get all the benefits

play03:20

of manual memory management control over

play03:23

our memory faster runtime and smaller

play03:25

program size rust however is also a

play03:27

memory safe language

play03:29

so we can't use manual memory management

play03:31

because as we said before

play03:33

it's air prone and as you can see here

play03:35

the ownership model is error-free

play03:36

and the way rust achieves this is by

play03:38

doing a bunch of compile time checks to

play03:40

make sure you're using memory

play03:42

in a safe way now i put astrix here

play03:44

because even though rust is memory safe

play03:46

by default it does allow you to

play03:48

opt out of memory safety with the unsafe

play03:50

keyword but that's meant to be used

play03:52

sparingly as you might know everything

play03:54

in software is a trade-off so the

play03:56

ownership model gives us memory safety

play03:58

but the con is we get a slower right

play04:00

time

play04:00

slower than manual memory management and

play04:02

that's because rust has a strict set of

play04:04

rules

play04:05

around memory management and if you

play04:07

break those rules you'll get compile

play04:08

time errors you have to deal with this

play04:10

is sometimes known as fighting with a

play04:12

borrower checker and it could be

play04:14

frustrating but as anything with time

play04:16

you'll get

play04:17

better and things will become easier the

play04:19

big idea here is that this trade-off

play04:21

is worth it it's worth spending the time

play04:24

up front dealing with the borrower

play04:25

checker so you don't have to spend hours

play04:27

and hours

play04:28

later debugging runtime memory issues

play04:30

now because

play04:31

rust is a systems programming language

play04:33

it's important for us to know how our

play04:35

memory is laid out

play04:36

during runtime rust makes certain

play04:38

decisions based on if our memory is

play04:39

stored on the stack or on the heap so

play04:41

next we'll briefly go over what the

play04:43

stack and heap are

play04:44

during runtime our program has access to

play04:46

both the stack

play04:47

and the heat the stack is fixed size and

play04:49

cannot grow or shrink during runtime the

play04:52

stack also stores stack frames which are

play04:54

created for every function that executes

play04:56

and the stack frame stores the local

play04:58

variables

play04:59

of the function being executed the size

play05:01

of a stack frame is calculated

play05:03

at compile time so that means the

play05:05

variables inside of stack frame must

play05:07

have a known

play05:08

fixed size variables inside of a stack

play05:10

frame also only live as long as the

play05:12

stack frame lives so for example

play05:14

in this program a gets executed first so

play05:17

we

play05:18

push a onto the stack then a executes b

play05:21

so we push the stack frame for b onto

play05:23

the stack now

play05:24

when b finishes executing it gets popped

play05:26

off the stack

play05:27

and all of its variables are dropped

play05:29

then when a gets done executing

play05:30

all its local variables are dropped the

play05:32

heap on the other hand

play05:34

is less organized it could grow or

play05:36

shrink at runtime

play05:37

and the data stored in the heap could be

play05:39

dynamic in size

play05:40

it could be large amounts of data and we

play05:43

control the lifetime

play05:44

of the data let's go back to our example

play05:46

first we execute the function a

play05:48

which creates a new stack frame a

play05:50

initializes the variables

play05:52

x and y x is a string literal which is

play05:54

actually stored

play05:55

in our binary so in the stack frame x

play05:57

here will be

play05:58

a reference to that string in our binary

play06:01

y

play06:01

is a sine 32-bit integer which is a

play06:04

fixed size so we can store y directly in

play06:06

the stack frame

play06:07

then we execute the function b so

play06:09

another stack frame

play06:10

is created and b creates its own

play06:13

variable named

play06:13

x x is of type string which could be

play06:16

dynamic in size so we can't store it

play06:18

directly on the stack instead we ask the

play06:20

heap to allocate memory for the string

play06:22

which it does and then the heap passes

play06:24

back

play06:24

a pointer the pointer is what we

play06:26

actually store on the stack

play06:27

note that pushing to the stack is faster

play06:30

than allocating

play06:31

on the heap because the heap has to

play06:33

spend time looking for a place to store

play06:35

the new data

play06:36

also note that accessing data on the

play06:38

stack is faster than accessing data on

play06:40

the heap because with the heap you have

play06:41

to follow the pointer

play06:42

i know that was a very brief explanation

play06:45

so if you're still confused or want a

play06:46

more thorough explanation

play06:48

i put a link in the description of a

play06:49

video that goes into a lot more detail

play06:51

on the stack and heap so make sure to

play06:53

check that out all right let's get back

play06:55

to ownership and before we go any

play06:57

further there are three ownership rules

play06:59

that are crucial to remember

play07:00

write these down put them in a word doc

play07:03

get them tattooed whatever it takes

play07:04

the rules are one each value in rust has

play07:07

a variable

play07:08

that's called its owner so one variable

play07:10

one owner

play07:11

two there can only be one owner at a

play07:14

time

play07:14

so variable cannot have two owners at

play07:16

the same time and three when the owner

play07:18

goes

play07:19

out of scope the value is dropped as an

play07:21

example i created a new scope here using

play07:24

the curly brackets and inside i defined

play07:26

the variable

play07:26

s here s is not valid because it's not

play07:29

declared yet then we declare

play07:31

s and it's valid from this point forward

play07:32

we could do things with s

play07:34

then when the scope ends s is

play07:36

invalidated and rust drops the value

play07:38

s here is a string literal and as i've

play07:40

mentioned before string literals are

play07:42

stored directly in the binary and are

play07:44

fixed in size so what if we wanted a

play07:47

string that's dynamic in size

play07:48

and that we could mutate well we would

play07:50

have to use the string type

play07:52

i went ahead and converted s to a string

play07:54

type and now our string is stored

play07:56

on the heap in programming languages

play07:58

such as c

play07:59

plus to allocate memory on the heap you

play08:01

would have to use the new keyword and

play08:03

then you would have to de-allocate that

play08:04

memory using the delete keyword when

play08:06

you're done with it

play08:07

in rust this happens automatically when

play08:09

we declare s here

play08:10

russ automatically allocates memory on

play08:12

the heap for our string and then when

play08:14

the scope ends

play08:15

s is invalidated and rust drops our

play08:18

value meaning that it deallocates the

play08:20

memory on the heap

play08:21

automatically next let's talk about how

play08:23

variables and data

play08:24

interact here we have two variables x

play08:27

and y x is set to 5

play08:28

and y is set to x and as you can tell by

play08:31

the comment this is going to do what you

play08:32

might expect which is copy the value 5

play08:35

into y let's look at a more interesting

play08:37

example here we have the variable s1

play08:39

and we set that equal to a string type

play08:41

on the right hand side you can see what

play08:43

s1 looks like under the hood we have a

play08:45

pointer that's pointing to the actual

play08:47

memory location on the heap we have a

play08:49

length which is the length of the string

play08:50

and we have capacity which is

play08:52

the actual amount of memory allocated on

play08:54

the heap for our string

play08:55

on the next line we declare s2 and set

play08:58

that equal to s1

play08:59

so what would we expect to happen in

play09:01

this situation

play09:02

some might expect the value to be cloned

play09:05

like we see on the right hand side

play09:06

s1 is pointing to a string on the heap

play09:09

and s2 is another pointer pointing to a

play09:11

new string

play09:12

on the heap but this is not what happens

play09:13

as it would be very expensive to create

play09:16

a new string on the heap others might

play09:17

think that we do a shallow copy

play09:19

so s1 has a pointer that points to hello

play09:22

on the heap

play09:22

and s2 has a pointer that points to the

play09:24

same hello on the heap

play09:26

this is not quite what happens because

play09:28

to ensure memory safety

play09:30

rust invalidates s1 so instead of being

play09:33

a shallow copy

play09:34

this is called a move going back to our

play09:36

program let's try to print

play09:38

s1 after it's been moved into s2

play09:41

we'll call cargo run

play09:44

and as you can see we got a compile time

play09:47

error which says

play09:48

s1 was moved here and then we tried to

play09:51

use

play09:51

s1 after it has what if we did actually

play09:55

want to clone the string instead of

play09:57

moving s1 well rust has a common method

play10:00

for that so instead of setting s2 equal

play10:02

to s1 we'll set

play10:03

s2 equal to s1 and we'll call the clone

play10:05

method on it

play10:07

and now we can run our program and it

play10:10

compiles successfully

play10:11

so rus defaults to moving a value and if

play10:13

you want to perform the more expensive

play10:15

clone operation there's a method for

play10:17

that there's one other

play10:18

detail here up above when we set y equal

play10:20

to x this did a copy not a move

play10:22

rust has a copy trait a simple type

play10:25

stored on the stack such as integers

play10:27

booleans and characters

play10:28

implement this trait allows those types

play10:30

to be copied instead of

play10:32

moved next let's talk about ownership

play10:33

and functions here we have a variable

play10:35

called

play10:36

s that's a string and a function called

play10:38

takes ownership the takes ownership

play10:40

function

play10:40

takes in some string as a parameter and

play10:43

then prints it out

play10:44

in main we try to print the variable s

play10:46

after we call it takes ownership but we

play10:48

get an

play10:49

error and the error states that s cannot

play10:51

be borrowed

play10:52

after a move and that's because when we

play10:54

pass in parameters into a function

play10:56

it's the same as if we were to assign s

play10:58

to another variable

play11:00

so here passing in s moves s into the

play11:03

sumstring

play11:04

variable then some string gets printed

play11:06

out and after this scope

play11:08

is done some string gets dropped let's

play11:10

look at another example

play11:12

we have the variable x here that is an

play11:14

integer

play11:15

and a function called makes copy makes

play11:17

copy takes in an integer

play11:18

and then prints it out here you can see

play11:20

we pass an x but instead of being moved

play11:22

remember integers are copied so it gets

play11:24

copied into the sum integer variable

play11:26

print it out but we can still use x

play11:28

after the function call this also works

play11:30

the opposite way

play11:31

here we have a variable s1 that's equal

play11:33

to the return value

play11:35

of gives ownership gives ownership is a

play11:37

function that returns

play11:38

a string and here you can see we create

play11:40

a string that's called some string

play11:42

and then we return that string returning

play11:44

the string moves the ownership

play11:45

of the string to the s1 variable and

play11:47

then we can use it afterwards

play11:49

lastly we could take ownership and give

play11:51

it back for example here we have a

play11:53

variable called

play11:54

s2 which is a string and we pass that

play11:56

into a function called takes and gives

play11:58

back takes and gives back

play11:59

takes in an argument that's a string

play12:02

called a string

play12:02

and then just returns a string so here

play12:04

we're moving the value of s2

play12:06

into the function and then we're just

play12:08

returning a string which moves the value

play12:10

back out of the function into s3 moving

play12:13

ownership

play12:14

into a function and back out is tedious

play12:16

what if we just wanted

play12:17

to use a variable without taking

play12:19

ownership well that's where references

play12:21

come in and that's what we'll talk about

play12:22

next

play12:23

to understand references let's see how

play12:25

they could fix the following situation

play12:27

here we have a function called calculate

play12:29

length what we want to do is take in a

play12:31

string

play12:31

and return the length of that string

play12:33

however we don't want to take ownership

play12:35

of the string the solution here is to

play12:37

return a tuple that

play12:38

contains the string and the length of

play12:40

the string up here you can see we assign

play12:43

the string to s2 and the length to a

play12:45

variable called

play12:46

len you could also see that this looks

play12:49

very strange and probably not something

play12:51

you would ever want to write

play12:52

to fix this let's first modify our

play12:54

calculate length function

play12:56

first we'll change the return type to be

play12:58

just the length of the string

play13:00

next we'll return just the length up

play13:03

above we'll get rid of

play13:04

s2 and print

play13:08

s1 down here

play13:11

as expected we get an error here because

play13:14

we're borrowing

play13:15

s1 after it's been moved into the

play13:17

function to fix this error instead of

play13:19

calculate length taking a string

play13:22

we'll make it take a reference to a

play13:23

string and we do that by adding an

play13:25

ampersand before the string

play13:29

next in the main function instead of

play13:30

passing in a string

play13:32

we'll pass in a reference to a string by

play13:34

using the ampersand again

play13:37

great now we have no errors and that's

play13:39

because s is a reference to a string

play13:42

and references don't take ownership of

play13:43

the underlying value

play13:45

here in the rest book you can see a

play13:46

diagram of what a reference looks like

play13:48

s is the reference and it points to s1

play13:51

which actually points to

play13:52

our string in the heap s is a local

play13:54

variable inside of calculate length and

play13:56

when the function finishes executing

play13:59

s is drop but that's okay because if we

play14:01

drop s here we still have

play14:02

s1 pointing to our string passing in

play14:05

references

play14:06

as function parameters is called

play14:07

borrowing because we're borrowing the

play14:09

value but we're not actually taking

play14:10

ownership of it

play14:11

also note that references are immutable

play14:14

by default so if we try to modify s here

play14:18

you can see that we get an error which

play14:20

says we cannot borrow the value as

play14:22

mutable here i have another example we

play14:24

have a string

play14:25

and a function called change which takes

play14:27

a reference to that string

play14:29

and then attempts to modify it again

play14:31

references are immutable by default so

play14:33

we can't modify

play14:34

the value but let's say we actually did

play14:36

want to modify the value without taking

play14:38

ownership of it to do that first we'll

play14:40

need to make s1

play14:41

immutable variable

play14:45

next instead of passing in a reference

play14:47

we're going to pass in

play14:48

a mutable reference finally we'll have

play14:52

the change function

play14:53

take an immutable reference

play14:57

now the change function is able to

play14:59

mutate our string

play15:00

without taking ownership of the

play15:02

underlying value mutable references

play15:04

have a big restriction though which is

play15:06

you can only have one mutable reference

play15:08

to a particular piece of data

play15:10

in a particular scope for example we

play15:12

have a string here and a variable called

play15:14

r1 which is immutable reference to the

play15:16

string now let's add another mutable

play15:18

reference and print out both references

play15:20

here we have r2 which is another mutable

play15:22

reference to string

play15:23

and then a print statement that prints

play15:25

out both r1 and r2 and you can see we

play15:27

get an error here the air states you

play15:29

cannot borrow

play15:30

s as immutable more than once at a time

play15:32

the big benefit of this restriction is

play15:34

that rest can prevent data races

play15:36

at compile time a data race occurs if we

play15:38

have two pointers pointing to the same

play15:40

piece of data

play15:41

and one of those pointers is used to

play15:43

write to the data and there's no

play15:45

mechanism to synchronize data access

play15:47

between those pointers

play15:48

in that situation you could imagine that

play15:50

one pointer will try to read the data

play15:52

in the middle of the other pointer

play15:54

modifying the data and in that case

play15:55

we'll get corrupt data back

play15:57

to fix this error we can switch these

play15:59

references back to be

play16:00

immutable references

play16:05

and now we don't have any errors but

play16:07

what happens if we mix

play16:08

immutable references with mutable

play16:10

references let's go ahead and add

play16:12

another variable we'll also go ahead and

play16:15

make

play16:16

s mutable but as you can see we still

play16:20

get an

play16:20

error here we're running into another

play16:22

restriction you can't have a mutable

play16:24

reference if an immutable reference

play16:26

already exists immutable references

play16:28

don't expect the underlying value to

play16:30

change and this is problematic if we do

play16:32

have a mutable reference

play16:33

you can however have multiple immutable

play16:36

references

play16:41

it's okay to have multiple immutable

play16:42

references because the underlying data

play16:44

is not going to change

play16:45

note that the scope of a reference

play16:47

starts when it's first introduced and

play16:49

ends when it's used for the last time so

play16:51

for example r1 is introduced here so the

play16:53

scope starts here and it's used for the

play16:55

last time

play16:56

in this print line statement so the

play16:58

scope ends here this means that we can

play17:00

add a third mutable reference

play17:02

underneath this print line statement

play17:05

and we'll make s mutable

play17:09

and this works just fine because at this

play17:12

point

play17:12

r1 and r2 are out of scope so we can

play17:15

declare

play17:15

a mutable reference next let's talk

play17:17

about dangling references

play17:19

what happens if we have a reference that

play17:21

points to invalid data well here we can

play17:23

see we have a variable called

play17:24

reference to nothing and it's set to the

play17:26

return value of dangle

play17:28

dangle is a function that returns a

play17:29

reference to a string inside of dangle

play17:31

we can see we have a variable called

play17:33

s which is set to a string and then we

play17:35

return a reference to that string

play17:37

however s is defined within the scope of

play17:39

this function so when this function

play17:41

finishes executing rust will drop our

play17:43

string or de-allocate our string from

play17:45

the heap

play17:46

therefore our reference will be pointing

play17:47

to invalid memory

play17:49

but as you could see russ prevents this

play17:50

from happening because we get an error

play17:52

here if we hover over we see that the

play17:54

error states this function's return type

play17:56

contains a borrowed value but there is

play17:58

no value for it to be borrowed from we

play18:00

don't have a value

play18:01

for it to be borrowed from because the

play18:02

value gets dropped

play18:04

we also get a recommendation to use

play18:06

something called a lifetime lifetimes is

play18:08

something we'll talk about

play18:09

in chapter 10 but for now we can ignore

play18:11

it the point here is that again

play18:12

russ prevents us from doing something

play18:14

that's memory unsafe

play18:16

to wrap up references let's review the

play18:18

rules of references

play18:19

number one at any given time for a

play18:21

particular piece of data in a particular

play18:23

scope you can either have one mutable

play18:25

reference or

play18:26

any number of immutable references and

play18:28

number two those references

play18:30

must be valid the data they point to

play18:32

must be valid

play18:34

the last thing we're going to talk about

play18:35

are slices slices let you reference a

play18:38

contiguous

play18:38

sequence of elements within a collection

play18:41

instead of referencing

play18:42

the entire collection and just like the

play18:44

references we covered in the last

play18:45

section

play18:45

slices do not take ownership of the

play18:47

underlying data

play18:48

to understand why slices are useful

play18:50

let's start with the problem

play18:52

imagine we have a function we want to

play18:53

write called first word

play18:55

takes in a reference to a string because

play18:57

we don't want to take ownership of that

play18:58

string

play18:59

and then it returns the first word in

play19:01

that string

play19:02

looking at the function signature what

play19:04

would our return type be

play19:05

we don't have a way to return part of

play19:07

the string but maybe we can return an

play19:09

index to the end of the first word

play19:11

that implementation will look something

play19:13

like this here we're returning an

play19:15

index to the end of the word and inside

play19:16

our function what we do is we take the

play19:18

string and convert it into an array of

play19:21

bytes

play19:21

then we use a foreign loop here we take

play19:24

the bytes call

play19:25

the iterator function and then call

play19:26

enumerate so we can get the index of

play19:28

each item and the item itself

play19:30

then for every item we check if it

play19:32

equals an empty space which signifies

play19:34

the end of a word and if that's true

play19:36

then we return that index if we go

play19:38

through this entire loop

play19:39

and don't find an empty space then that

play19:41

means that the entire string

play19:43

is one word so we can return the length

play19:45

of the string there are two problems

play19:46

with this implementation

play19:48

the first is that the return value is

play19:50

not tied to the string and here's what i

play19:51

mean

play19:52

in main we have a string under the

play19:54

variable s

play19:55

we pass that string to first word and we

play19:58

store the return value

play19:59

in a variable called word in this case

play20:02

our return value

play20:03

is 5 because our first word is hello

play20:06

h is at index 0 then we have e at index

play20:09

1

play20:10

ll index 2 and 3 o at index 4 and the

play20:13

space is at index

play20:14

5. now the problem is on line 4 we call

play20:16

the clear method on our string which

play20:18

makes it an

play20:19

empty string so now our variable word is

play20:21

still

play20:22

5 even though the string is empty and

play20:24

again this is because

play20:25

our return value is not tied to the

play20:27

string itself this means we have to

play20:29

manually keep a return value

play20:31

in sync with the string which is

play20:32

extremely error prone second problem

play20:34

comes when we want to change the

play20:36

implementation imagine instead of the

play20:38

first word we wanted to return the

play20:39

second word

play20:40

in that case we would have to return a

play20:42

tuple with two values

play20:43

an index to the start of the word and an

play20:46

index to the end of the word and now we

play20:47

have

play20:48

more values that we need to keep in sync

play20:50

with the string to get around these

play20:51

issues let's introduce the string slice

play20:54

here underneath our string we'll declare

play20:56

two string slices

play20:59

here we've defined two string slices

play21:01

hello and world

play21:02

they look very similar to string

play21:04

references except we have this bracket

play21:06

syntax here with

play21:07

a range inside this is saying give us a

play21:10

reference to

play21:11

the string but we only want part of the

play21:13

string specifically we want the part of

play21:15

the string starting from index 0

play21:17

and ending at index 4. the 5 is

play21:19

exclusive here and that will give us the

play21:21

word hello

play21:22

on the second line we're doing something

play21:23

similar but from index 6

play21:25

to 11 which will give us world the rust

play21:28

book has a nice diagram to show us

play21:30

what's going on

play21:31

here we have s which is our string and

play21:34

you can see it has a pointer pointing to

play21:36

the string on the heap and then

play21:39

world is our string slice that's a

play21:41

reference

play21:42

pointing to the same string on the heap

play21:44

but starting at index six

play21:46

we can simplify these string slices a

play21:48

bit further if we're starting from the

play21:50

beginning of the string we can

play21:51

omit the zero here and if our range

play21:55

continues to the end of the string we

play21:56

can omit 11

play21:58

here as well lastly if we want a string

play22:01

slice to span the entirety of the string

play22:03

we can omit the first and second value

play22:05

here

play22:07

in this case world is a string slice

play22:09

that will reference

play22:10

the entire string now that we know about

play22:12

string slices let's modify

play22:14

our first word function to take

play22:16

advantage of them the first thing we'll

play22:17

do is

play22:18

change the return type from the index to

play22:20

a string

play22:21

slice next if the first word is found

play22:25

instead of returning the index we'll

play22:27

return a string slice

play22:30

here we're returning a string slice from

play22:32

the beginning of the word to the index

play22:34

where the space was found

play22:36

lastly at the end here instead of

play22:37

returning the length of the string will

play22:39

return a string

play22:40

slice to the entire string

play22:44

now our word variable is a string slice

play22:46

which is tied to

play22:47

the string itself and to prove that

play22:49

let's try printing out

play22:50

word after we clear the string

play22:54

and here we can see we get an error

play22:56

cannot borrow s as mutable because

play22:58

it is already borrowed as immutable word

play23:01

is a string slice which is an immutable

play23:02

reference

play23:03

to string and here we call clear which

play23:06

mutates the string that means we need a

play23:07

mutable reference

play23:09

and if you recall we cannot mix

play23:10

immutable and immutable references in

play23:12

the same scope

play23:13

let's get rid of the code that's

play23:14

erroring

play23:16

and we'll create a new variable called

play23:20

s2

play23:23

and we'll set it to a string literal

play23:25

hello world as you can see string

play23:27

literals are actually

play23:28

string slices as i mentioned before

play23:31

string literals are stored

play23:32

directly in the binary but s2 here is

play23:35

actually a string slice to that location

play23:37

in the binary now let's say we wanted

play23:39

our first word function to also work

play23:41

with string literals

play23:42

in that case it would have to take in a

play23:44

string slice so let's change that

play23:49

notice that our call to first word here

play23:52

kept working

play23:52

this is because our string reference

play23:55

gets automatically coerced to a string

play23:57

slice

play23:57

and now we can pass in s2

play24:01

and our function still continues to work

play24:03

and notice here that

play24:04

strings have the type of string with a

play24:06

capital s

play24:07

and string slices have their type

play24:08

written as ampersand lowercase

play24:10

str we can also have slices on different

play24:13

types of collections for example we have

play24:15

an array here

play24:16

let's say we want to create a slice on

play24:18

this array so we'll create a new

play24:20

variable called slice

play24:24

and we'll set that equal to a portion of

play24:25

the a array

play24:33

here you can see we use a similar syntax

play24:35

we have the ampersand

play24:37

a for the array and then brackets and

play24:39

here we do

play24:40

a range from 0 to 2. so our slice here

play24:43

references the first two

play24:44

values in the a array and you can see

play24:46

here instead of the type being

play24:48

ampersand str it's ampersand curly

play24:51

brackets

play24:51

i32 because the array stores a list of

play24:55

signed 32-bit integers

play24:56

and there you have it chapter 4 of the

play24:58

wrestling book complete

play25:00

we learned about ownership memory

play25:02

management references

play25:03

borrowing the slice type and much more

play25:06

if you like this video

play25:07

make sure to give it a thumbs up and if

play25:09

you want to see more rust content

play25:11

make sure to hit subscribe and the

play25:13

notification bell so you can be notified

play25:15

when the next video comes out i'll see

play25:18

you

play25:18

in the next one

play25:29

you

Rate This

5.0 / 5 (0 votes)

Related Tags
Rustプログラミング所有権メモリ管理ガベージコレクション手動メモリシステムプログラミングエラー回避パフォーマンス安全性
Do you need a summary in English?