English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Компьютерные программы должны управлять используемыми ими ресурсами памяти в процессе выполнения.
Большинство языков программирования имеют функцию управления памятью:
Языки, такие как C/C++, управляют памятью主要通过 ручной способ, где разработчикам необходимо вручную запрашивать и освобождать ресурсы памяти. Однако для повышения эффективности разработки, если это не влияет на реализацию функции программы, многие разработчики не имеют привычки своевременно освобождать память. Поэтому ручное управление памятью часто приводит к浪费у ресурсов.
Программы, написанные на языке Java, выполняются в виртуальной машине (JVM), которая обладает функцией автоматического回收 ресурсов памяти. Однако этот метод часто снижает производительность выполнения, поэтому JVM старается минимизировать количество回收аемых ресурсов, что также приводит к увеличению объема используемых программой ресурсов памяти.
Концепция собственности является новой для большинства разработчиков и является языковой конструкцией, разработанной языком Rust для эффективного использования памяти. Концепция собственности появилась для того, чтобы Rust мог более эффективно анализировать ресурсы памяти на этапе компиляции, чтобы обеспечить управление памятью.
Собственность руководствуется тремя правилами:
У каждого значения в Rust есть переменная, называемая его владельцем.
Одновременно может быть только один владелец.
Если владелец не находится в области выполнения программы, это значение будет удалено.
Эти три правила являются основой концепции собственности.
Далее будут рассмотрены концепции, связанные с понятием собственности.
Мы используем следующий фрагмент программы для описания концепции области переменных:
{ // Перед объявлением переменная s недействительна let s = "w3codebox"; // Это область действия переменной s } // Область действия переменной уже закончилась, переменная s недействительна
Область действия переменной это свойство переменной, представляющее ее возможную область, по умолчанию действителен от момента объявления переменной до окончания области действия переменной.
Если мы определяем переменную и присваиваем ей значение, это значение существует в памяти. Это очень распространенное явление. Но если длина данных, которые нам нужно хранить, не определена (например, строка, введенная пользователем), мы не можем определить длину данных при определении и не можем определить фиксированный объем памяти, который программа будет использовать для хранения данных на этапе компиляции. (Кто-то говорит, что можно выделить как можно больше места, но этот метод нецивилизован). Для этого необходимо предоставить механизм, который позволяет программе запрашивать использование памяти в режиме выполнения. Все "ресурсы памяти", о которых идет речь в этой главе, это память, занимаемая кучей.
Если есть выделение, то есть и освобождение, программа не может постоянно занимать какие-либо ресурсы памяти. Поэтому ключевым фактором для определения того, была ли ресурса потрачена зря, является своевременное освобождение ресурсов.
Мы пишем пример программы с использованием языка C:
{ char *s = "w3codebox"; free(s); // Освобождение ресурсов s }
Конечно, в Rust не вызывается функция free для освобождения ресурсов строки s (я знаю, что это неправильное написание в C, потому что "w3codebox" не находится на куче, здесь предполагается, что он есть). Rust не требует явного освобождения, потому что компилятор Rust автоматически добавляет вызов функции освобождения ресурсов в конце области действия переменной.
Этот механизм кажется очень простым: он всего лишь помогает程序员 добавить вызов функции освобождения ресурсов в нужное место. Но этот простой механизм эффективно решает одну из самых头痛ящих проблем программирования для программистов.
Основные способы взаимодействия переменных с данными это перемещение (Move) и клонирование (Clone):
Множественные переменные могут взаимодействовать с одним и тем же данными по-разному в Rust:
let x = 5; let y = x;
Эта программа связывает значение 5 с переменной x, затем копирует значение x и присваивает его переменной y. Теперь на стеке будут два значения 5. В этом случае данные являются типом "базовых данных", их не нужно хранить на куче, они хранятся только на стеке, и способ "перемещения" данных это的直接 копирование, что не требует больше времени или места для хранения. Типы "базовых данных" включают в себя:
Все типы целых чисел, например i32, u32, i64 и т.д.
Тип данных bool, значение true или false.
Все типы плавающей точки, f32 и f64.
Тип данных char.
Только кортежи (Tuples), содержащие данные из указанных выше типов.
Но если данные для взаимодействия находятся в куче, то это другое дело:
let s1 = String::from("hello"); let s2 = s1;
Шаг первый создает объект String с значением "hello". "hello" можно рассматривать как данные с неопределенной длиной, которые необходимо хранить в куче.
Второй случай несколько иной (Это не совсем правда, используется только для сравнения и参照а):
如图所示: два объекта String в стеке, каждый объект String имеет указатель на строку "hello" в куче. При присвоении значения s2 только данные в стеке копируются, строка в куче остается прежней.
Как мы говорили ранее, когда переменная выходит за пределы области видимости, Rust автоматически вызывается функция высвобождения ресурсов и очищает кучу памяти переменной. Но если высвободить s1 и s2, строка "hello" в куче будет высвобождена дважды, что не разрешается системой. Чтобы обеспечить безопасность, s1 уже не действует при присвоении значения s2. Да, после присвоения значения s1 ему больше не будет можно использовать. Следующее программное обеспечение является неправильным:
let s1 = String::from("hello"); let s2 = s1; println!("{}, world!", s1); // Ошибка! s1 уже не действует
Таким образом,实际情况 такова:
s1 на самом деле мертв.
Rust стремится максимально снизить затраты на выполнение программы, поэтому по умолчанию данные с большой длиной хранятся в куче и передаются с помощью метода перемещения. Но если нужно создать копию данных для использования в других целях, можно использовать второй способ взаимодействия с данными - клонирование.
fn main() { let s1 = String::from("hello"); let s2 = s1.clone(); println!("s1 = {}, s2 = {}", s1, s2); }
Результат выполнения:
s1 = hello, s2 = hello
Здесь действительно создается копия строки "hello" в куче, поэтому s1 и s2 привязаны к различным значениям, и при их освобождении они также будут считаться двумя ресурсами.
Конечно, клонирование используется только в том случае, если необходимо создать копию, так как копирование данных требует больше времени.
Для переменных это наиболее сложная ситуация.
Как безопасно обрабатывать обладание, если передавать переменную в другую функцию в качестве аргумента?
Следующее программное описание объясняет принцип работы механизма обладания в этой ситуации:
fn main() { let s = String::from("hello"); // s объявлен валидным takes_ownership(s); // Значение s传入ет в функцию в качестве аргумента // Поэтому можно считать, что s уже перемещен и больше не действителен let x = 5; // x объявлен валидным makes_copy(x); // Значение x传入ет в функцию в качестве аргумента // Но x является базовым типом и все еще действителен // Здесь все еще можно использовать x,но не s } // Функция завершена, x не действителен, затем s. Но s уже перемещен, поэтому его не нужно аннулировать fn takes_ownership(some_string: String) { // Передан аргумент String some_string,действителен println!("{}", some_string); } // Функция завершена, аргумент some_string в此处 аннулируется fn makes_copy(some_integer: i32) { // Передан аргумент 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 { let some_string = String::from("hello"); // some_string является действительным return some_string; // some_string переносится из функции в качестве значения возвращения } fn takes_and_gives_back(a_string: String) -> String { // a_string является действительным a_string // a_string переносится из функции в качестве значения возвращения }
Право собственности на переменную, возвращаемую в качестве значения функции, будет передано из функции и возвращено в место вызова функции, а не будет напрямую аннулировано.
Ссылка (Reference) - это концепция, с которой знакомы разработчики C++.
Если вы знакомы с концепцией указателей, вы можете рассматривать это как вид указателя.
Фактически "использование" - это косвенный доступ к переменным.
fn main() { let s1 = String::from("hello"); let s2 = &s1; println!("s1 is {}, s2 is {}", s1, s2) }
Результат выполнения:
s1 is hello, s2 is hello
Оператор & может получить "ссылку" на переменную.
Когда значение переменной ссылается на нее, переменная herself не признается недействительной. Porque "ссылка" не копирует значение переменной в стек:
Принцип передачи параметров функции аналогичен:
fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); println!("Длина строки '{}' составляет {}.", s1, len); } fn calculate_length(s: &String) -> usize { s.len() }
Результат выполнения:
Длина строки "hello" составляет 5.
Ссылка не получает право собственности на значение.
Ссылка может только сдавать в аренду право собственности на значение.
Ссылка herself также является типом и имеет значение, которое записывает положение других значений, но ссылка не имеет права собственности на所指анное значение:
fn main() { let s1 = String::from("hello"); let s2 = &s1; let s3 = s1; println!("{}", s2); }
Этот фрагмент программы неправильный: потому что s2, арендовавший s1, уже переместил право собственности к s3, поэтому s2 не сможет продолжить аренду использования права собственности s1. Если нужно использовать значение s2, необходимо повторно арендовать:
fn main() { let s1 = String::from("hello"); let mut s2 = &s1; let s3 = s2; s2 = &s3; // повторно арендовать право собственности у s3 println!("{}", s2); }
Этот фрагмент программы правильный.
Поскольку ссылка не имеет права собственности, даже если она арендует право собственности, она имеет только право использования (это похоже на сдачу дома в аренду).
Если попытка использовать право аренды для изменения данных будет блокироваться:
fn main() { let s1 = String::from("run"); let s2 = &s1; println!("{}", s2); s2.push_str("oob"); // ошибка, изменение значения аренды запрещено println!("{}", s2); }
В этом фрагменте программы попытка изменить значение s1 через s2 блокируется, право аренды не позволяет изменять значение собственника.
Конечно, существует также способ срочной аренды, как если бы вы сдавали в аренду дом, и если управляющий имуществом позволяет собственнику изменять структуру дома, собственник также указывает в договоре, что вы имеете право на такие изменения, и вы можете капитально ремонтировать дом:
fn main() { let mut s1 = String::from("run"); // s1 является изменяемым let s2 = &mut s1; // s2 является изменяемым ссылочным s2.push_str("oob"); println!("{}", s2); }
Этот код без проблем. Мы используем &mut для обозначения mutable reference типа.
Кроме прав доступа, mutable reference не позволяет многоразовое использование, в то время как immutable reference может:
let mut s = String::from("hello"); let r1 = &mut s; let r2 = &mut s; println!("{}, {}", r1, r2);
Этот код является неправильным, потому что s имеет несколько mutable reference.
Этот дизайн Rust для mutable reference в основном вызван учетом возможных столкновений доступа к данным в условиях конкуренции, и такие столкновения предотвращаются на этапе компиляции.
Одним из необходимых условий для возникновения конфликта доступа к данным является то, что данные должны быть至少 одним пользователем записаны и, в то же время, должны быть прочитаны или записаны хотя бы одним другим пользователем, поэтому не допускается повторное использование значения, которое уже имеет переменную mutable reference.
Это концепция, которая в языках программирования с концепцией指针 может означать тот тип指针, который не指向 реальное данные, доступные для доступа (обратите внимание, это не обязательно пустой указатель, это также может быть уже освобожденные ресурсы). Они как бы висят на веревке, поэтому их называют «подвешенными ссылками».
«Подвешенная ссылка» в языке Rust не допускается, если она существует, компилятор обнаружит ее.
Ниже приведен пример典型案例 подвешенной ссылки:
fn main() { let reference_to_nothing = dangle(); } fn dangle() -> &String { let s = String::from("hello"); &s }
Конечно, с завершением функции dangle, значение локальной переменной само по себе не было использовано в качестве возвращаемого значения, и было освобождено. Но ссылка на нее была возвращена, и значение, на которое она указывает, уже не может быть определено, поэтому его的出现 не разрешается.