Владение Rust: новая школа управления памятью

Java Безопасность Rust C++
Владение Rust: новая школа управления памятью

В Rust каждое значение может принадлежать только одному владельцу, и когда значение назначается другому владельцу, первоначальный владелец больше не может его использовать. Именно этот механизм гарантирует сохранность памяти языка Rust, устраняя необходимость в автоматической сборке мусора и ручном освобождении. Владение — одна из самых важных особенностей Rust. Давайте рассмотрим простой пример.

fn main() {
    let s = String::from("hello");
    let a = s;          // 字符串对象“hello” 的所有权被转移
    println!("{}", s); // error! 无法再使用 s
}
 

Базовый случай перехода права собственности показан выше, посколькуaвзял на себя ответственность за строку,sбольше нельзя использовать. Это может сильно отличаться от существующих языков программирования, на самом деле это было совершенно верно для всех языков, которые я знал до Rust, но теперь я вижу магию в этом.

Зачем передавать право собственности?

Rust нацелен на язык системного программирования, который требует безопасности памяти и управления памятью без накладных расходов во время выполнения.

Каковы накладные расходы во время выполнения на управление памятью?Давайте возьмем в качестве примера Java.Java утверждает, что является языком, безопасным для памяти, потому что программистам на Java не нужно управлять памятью вручную (собственное управление программистами является источником небезопасности памяти), и Java использует мусор Автоматическая переработка, все Java-программы запускаются в JVM.Во время выполнения Java-программы JVM должна всегда отслеживать и обходить дерево объектов Java, чтобы определить, на какие переменные в куче больше нет ссылок, и автоматически освобождать их, когда определенный период времени поступает память для переменных, на которые нет ссылок.

Другими словами, JVM выполняет процесс сборки мусора, совершенно не связанный с реальной программой, что называется накладными расходами во время выполнения. Конечно, из-за позиционирования Java эти накладные расходы можно полностью игнорировать.

C/C++, как язык системного программирования, вручную управляет памятью программиста.mallocЗаднийfreeДолжен использоваться, иначе произойдет утечка памяти, в конечном итоге занимающая все пространство памяти процесса, в C++ этоnewиdeleteЭта пара хороших братьев. Визуально это работает нормально, если вы помните об использовании обоих, но когда проблема усложняется, она становится сложной и подверженной ошибкам. Взгляните на кусок кода C:

#include <stdio.h>
#include <malloc.h>
 
int* func()
{
    int *p = malloc(sizeof(int));
    /*
       do something
    */
    return p;
}
 

Может ли кто-нибудь гарантировать, что вне этой функции кто-то помнит, что указатель указывает на кучу, а не на стек, и не забывает вызыватьfree()? Трудно сказать, особенно когда ситуация усложняется... Поэтому стать мастером C/C++ сложно, и даже у Гула с этим проблемы, пытаясь его использоватьGoЗамена некоторых сценариев приложений C/C++,GoЯ не буду упоминать здесь особенности. Это, несомненно, отличный язык программирования. Он происходит из известного семейства. Хотя это язык системного программирования, в настоящее время он в основном используется для сетевого программирования. Он также использует автоматическую сборку мусора. Таким образом, накладные расходы во время выполнения неизбежны.

Два упомянутых выше примера Java и C/C++ представляют две школы современного управления памятью.У обоих методов есть определенные болевые точки, поэтому Rust решил принять совершенно другой метод управления.Передавая право собственности, Rust обеспечивает безопасное управление памятью. Итак, теперь вернемся к теме и посмотрим, как Rust управляет памятью.

Управление памятью в Rust

В Rust нет автоматической сборки мусора (Auto GC) и не требуется ручное управление, эта работа находится на этапе компиляции и выполняется компилятором. После успешной компиляции, когда переменная память была освобождена, она была определена, жестко закодирована в двоичную программу, и программа автоматически освободится, когда она запустится до освобожденного времени. Как компилятор может быть таким умным? Подвиг систем владения в Rust не имеет себе равных. Различные особенности системы собственности описаны ниже.

сфера

Каждая переменная ограничена областью видимости, и, как и в большинстве языков программирования,{}рассматривается как флаг области действия, но разница в том, что при запуске}Когда освобождаются не только переменные в стеке, но и память в куче.

fn func() {
    let n = 2;  // 在栈上分配
    let s = String::new(); // 在堆上分配
}
 

Как и выше, пространство, выделенное в куче, также освобождается, что кажется нормальным, но еслиsиспользуется в качестве возвращаемого значения, его область действия изменилась, оно все еще может оказаться в каком-то}(в конце области) освобождается, и область действия гарантирует, что переменная будет переработана, что позволяет избежать забывания вызвать язык C вышеfree()ситуация.

Как это перерабатывается? В Rust классы используютstructопределение, или вы можете назвать это «класс», но что-то еще. Каждый объект реализуетtrait, которыйDrop(Если вы знакомы с Java, вы можете поставитьtraitпонимается как «интерфейс»),Dropсодержит методdrop(), когда какой-либо объект выходит из области видимости, егоdrop()будет вызываться автоматически для освобождения собственной памяти.

перечислить

Как упоминалось в начале этой статьи, у значения может быть только один владелец, поэтому при присвоении другой переменной право собственности передается, и первоначальный владелец больше не может его использовать. В Rust передача права собственности называетсяmove, в начале этой статьи赋值— это базовый пример передачи права собственности, давайте рассмотрим более сложный пример.

fn main() {
    let s = String::from("hello");
    func(s);    // 字符串的所有权转移到了func()的内部
    let a = s;  // error  s 已经无法被使用
}
 
fn func(s: String) {
    println!("{}", s);  // s 将在离开作用域时被释放
}
 

Но иногда после использования в качестве параметра функции все равно хочется использовать что делать, ставить в конец функцииreturnЭто решение, но не очень хорошее решение, о чем мы вскоре поговорим借用, очень хорошо решит эту проблему.

Примечательно,moveПримеры, которые я использовал,String::new()或者String::from()для создания строкового объекта, почему бы просто не использовать, например, строковый типlet s = "hello"или другие типы, такие какi32демонстрировать, потому чтоmoveПравила на них не распространяются!

fn main() {
    let n = 2;   // i32 类型
    let a = n;
    println!("{}", n);  // success! 并没有问题
}
 

Кажется, что это противоречит предыдущей теории, но на самом деле правила владения распространяются на все типы, кроме того, что на Rust в целях уменьшения сложности программирования, в базовом типе赋值, копирует копию исходной памяти в качестве новой переменной вместо передачи права собственности. То есть в этом примереaявляется независимой переменной с независимой памятью, а не изnполучено в.nПамять для соответствующего значения также резервируется, чтобы ее можно было продолжать использовать.

Каковы «основные типы», упомянутые выше, и наиболее часто используемыеi32, boolи так далее. В частности, реализуетсяCopyэтоtraitТип базового типа Rust имеет встроенную реализацию, то есть мы можем полностью создать свой собственныйStringТип реализацииCopy, чтобы при назначении строкового объекта копия не передавалась.

Уведомление!let s = "hello"серединаsне является переменной базового типа, хотя присваивание не передает права собственности, потому чтоsТип&str, Да借用Работайте, а не копируйте!

выше赋值и传参даmoveНеявный вызов в некоторых случаях должен передавать ключевое словоmoveЯвно указано, иначе не скомпилируется, например闭包это обычная ситуация. Учитывая объем статьи, я не буду ее здесь представлять.闭包, давайте быстро перейдем к ранее упомянутому множеству借用, которая также является последней частью этого раздела.

заимствовать

Использование передачи права собственности иногда слишком обременительно, так как в реальности я могу одолжить свои вещи другим, но при этом иметь право собственности, Rust поддерживает заимствование (borrow),использовать&Указывает, что некоторые статьи также называются "цитаты", но я не думаю, что это хорошо, потому что здесь&с в C/С++&Есть большая разница, и компиляторы называют это «занять» вместо «ссылаться»!

Давайте посмотрим, как решить проблему собственности с помощью заимствования.

fn main() {
    let s = String::from("hello");
    let a = &s;
    println!("{}", s);  // success
    println!("{}", a);  // success, print "hello"
    func(&s);  // success
}
 
fn func(s: &String) {
    println!("{}", s);
}
 

Этот код является основным использованием заимствования,aпройти через&заимствованныйsпамяти, и не переносился, а теперьaможет получить доступsПространства больше нет, Rust позволяет использовать несколько заемщиков, переданных в функциюfunc()Да, тожеsзаимствование, но это вfunc()был выпущен в конце.

Однако при заимствовании владельцу не разрешается изменять переменную или передавать право собственности! Это может показаться вопросом этикета, но на самом деле это вопрос безопасности памяти.Изменение значения приведет к тому, что эти заимствованные значения будут несовместимы сами с собой, вызывая логические ошибки, а передача права собственности неизбежно приведет к сбою заимствования .Поэтому это не допускается! давай попробуемfunc(&s)Затем право собственности передается.

fn main() {
    let s = String::from("hello");
    ...
    func(&s);
    let b = s;  // error  s 已被借用,无法转移
}
 

До сих пор упомянутое заимствование относится к неизменяемому заимствованию, которое также можно назвать «заимствованием только для чтения». Допускается несколько заимствований, и заемщику не разрешается изменять значение. Это нетрудно понять. заемщик изменяет Если значение изменено, это неизбежно приведет к несогласованности данных.

Но иногда нам действительно нужно изменить значение! такие, как мы знакомыswap(), тогда Rust предоставляет可变借用(&mut), Переменный заемщик может изменить данные, конечно, предпосылка состоит в том, что само значение является переменным (mut). Для краткого рассмотрения здесь мы используем конкатенацию строк в качестве примера.

fn main() {
    let mut s = String::from("hello");
    func(&mut s);
}
 
fn func(s: &mut String) {
    s.push_str(" world");  // s = "hello world"
}
 

С переменным заимствованием,func()функция была измененаs, но изменяемые заимствования имеют очень строгое ограничение: может быть только одно изменяемое заимствование. В течение переменного периода заимствования не допускается никакое другое заимствование, в том числе неизменяемое заимствование, в течение переменного периода заимствования владелец не может совершать какие-либо операции, передавать или изменять стоимость.

С определенной точки зрения изменяемое заимствование ничем не отличается от передачи, оно эквивалентно временной передаче права собственности: когда переменная, получающая передачу, выходит из области видимости, право собственности автоматически возвращается к первоначальному владельцу.

Пока система владения Rust в основном введена, именно эти правила держат знамя безопасности памяти Rust, полные и взаимно продемонстрированные,借用Теория исходит из жизни, что имеет смысл.Я должен сказать, что дизайн управления памятью в Rust очень тонкий!

Tips

Извлеките несколько полезных советов из этой статьи:

  • Базовые переменные (реализованыCopy) не передает права собственности, а копирует
  • В течение заимствованного периода изменение значения не допускается
  • Допускается заимствование только одной переменной.В течение периода заимствования владелец не разрешает никаких операций
  • Переменное заимствование эквивалентно временной передаче права собственности.После освобождения заимствования имущество возвращается к первоначальному владельцу.

Заканчивать.

× Если воспроизводится, пожалуйста, укажите источник, спасибо ×