Я не понимаю, почему переменная должна быть клонирована и не ссылаться - PullRequest
0 голосов
/ 09 апреля 2020

Я читаю Язык программирования Rust . В этом коде я не понимаю, почему это должно быть args[1].clone() и почему оно не может быть &args[1]:

use std::env;
use std::fs;

fn main() {
    let args: Vec<String> = env::args().collect();

    let config = parse_config(&args);

    println!("Searching for {}", config.query);
    println!("In file {}", config.filename);

    let contents = fs::read_to_string(config.filename)
        .expect("Something went wrong reading the file");

    println!("With text:\n{}", contents);
}

struct Config {
    query: String,
    filename: String,
}

fn parse_config(args: &[String]) -> Config {
    let query = args[1].clone();
    let filename = args[2].clone();

    Config { query, filename }
}

Книга объясняет это, но я все еще не понимаю. Что-то говорит о том, что структура становится владельцем.

Это то же самое, что и код выше? Это то, что сказал компилятор, когда я изменил args[1].clone на &args[1]

fn parse_config(args: &[String]) -> Config {
    let query = &args[1];
    let filename = &args[2];

    Config { query: query.to_string(), filename: filename.to_string() }
}

Ответы [ 2 ]

1 голос
/ 15 апреля 2020

Поскольку вы упомянули "структура, берущая на себя ответственность или что-то подобное", я собираюсь немного отклониться от вашего вопроса, чтобы немного прояснить ситуацию.

Упрощенный обзор владения

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

struct Bike {
   brand: String;
   tire: f32;
}

impl Bike {
   fn ride_bike(&self) {
       println!("Goin' where the weather suits my clothes!")
   }
}

fn main() {
   // You are the owner of the bike
   let rafaels_bike = Bike { 
      brand: String::from("Bike Friday"), 
      tire: 28.0 
   }

   // You are giving your bike to me
   let eduardos_bike = rafaels_bike

   // Since you gave the bike to me, you cannot ride it anymore
   // rafaels_bike.ride_bike()    // It will give a borrow after move error, if uncommented

   // The bike is mine now, so I can ride it.
   eduardos_bike.ride_bike()
}

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

Эта концепция владения пронизывает все операции в Rust, включая объявления структур и функций, выражения и т. Д. c. В результате способ объявления функций может дать вызывающей стороне информацию о том, что вы хотите сделать с данными.

// Immutable borrow: You're giving me permission to use your data (Bike), 
// but I can't change it
fn have_a_look(bike: &Bike) 

// Mutable borrow: You're giving me permission to modify your data (Bike)
fn install_modifications(bike: &mut Bike) 

// Moving operation: You're giving the bike away, you cannot use it anymore
// after you call the function
fn give_to_charity(bike: Bike)     // Bike is still available inside the function

Это также относится к объявлению структуры, если вы объявляете атрибут без & позади него говорит, что хочет владеть данными **, поэтому структура Bike говорит, что хочет владеть данными бренда String. Что ж, аналогия с велосипедом здесь не работает, поэтому давайте вернемся к вашему вопросу.

** Здесь есть небольшое несоответствие, которое относится к значениям, которые реализуют черту Copy . Если значение реализует свойство копирования, ржавчина будет копировать его везде, где только можно, поэтому вам не нужно беспокоиться о том, чтобы поставить & перед типом, как это происходит для f32 и многих других баз c типы.

Проблема под рукой

Чтобы понять, почему вы не можете делать то, что хотите (используйте &args[1] вместо args[1].clone()), нам нужно помнить о правила владения в ржавчине :

  1. Каждое значение в Rust имеет переменную, которая называется его владельцем.
  2. В каждый момент времени может быть только один владелец.
  3. Когда владелец выходит за рамки, значение будет сброшено.

Имея это в виду, давайте посмотрим на функцию parse_config, если она была реализована так, как вы хотите :

// The Config struct wants to own both the query and filename strings
struct Config {
    query: String,
    filename: String,
}

// You're saying that you want to look at the args data and nothing else
fn parse_config(args: &[String]) -> Config {
    let query = &args[1];
    let filename = &args[2];

    // Then you're trying to give it away to Config behind the caller's back
    // You broke the contract
    Config { query, filename }
}

По сути, если бы это было разрешено, у Strings в args было бы два владельца: переменные args и config, нарушающие правило № 2 владения.

Давайте предположим, что ржавчина не была настолько строгой, и правила № 2 не существовало. В этой гипотезе это было бы возможно:

// This is a world without rule n° 2, this code will not compile.
use std::env;
use std::fs;

fn main() {
    // args own the String inside the Vec
    let args: Vec<String> = env::args().collect();

    let contents = {
        // config will also own the Strings at index 1 and 2
        let config = parse_config(&args);

        println!("Searching for {}", config.query);
        println!("In file {}", config.filename);

        fs::read_to_string(config.filename)
            .expect("Something went wrong reading the file")

        // config goes out of scope, cleaning the memory for strings it owns
    };

    // The strings at index 1 and 2 don't exist anymore
    // This is known as a dangling pointer
    println!("{:?}", args);

    println!("With text:\n{}", contents);

    // End of scope: Will try to clean args memory, but some of it was already cleaned
    // This is known as a double-free
}

struct Config {
    query: String,
    filename: String,
}

fn parse_config(args: &[String]) -> Config {
    let query = &args[1];
    let filename = &args[2];

    // Give the strings to Config anyway
    Config { query, filename }
}

Как видите, все сломалось бы, и Rust не сможет больше гарантировать безопасность памяти.

Решения

Клонирование

Решение, которое предлагает книга, заключается в клонировании строк, что означает, что вы выделяете целый новый блок памяти, отличный от того, которым владеет args:

    let query = args[1].clone();
    let filename = args[2].clone();

Теперь и config, и args в порядке, поскольку им принадлежат совершенно разные блоки памяти. Когда они go выйдут из области видимости, каждый уберет то, что у них есть.

Переход к функции

Этот уже упоминался. Вы можете переместить переменную args в главном параметре функции. Поскольку теперь он владеет данными, вы можете делать с ним все, что захотите, включая передачу прав собственности на другую структуру.

fn parse_config(mut args: Vec<String>) -> Config

Это работает, потому что функция main не использует переменную args после parse_config называется. Вы все равно не можете использовать индексирование, потому что вы не можете выйти из индекса, поскольку это происходит с помощью неизменного заимствования:

из документации по признаку индекса :

fn index(&self, index: Idx) -> &Self::Output

Использование to_string ()

Этот, который вы пришли сами, и да, он функционально эквивалентен предоставленному в книге.

Реализация to_string для String следующим образом:

// to_string is implemented by means of to_owned
#[stable(feature = "string_to_string_specialization", since = "1.17.0")]
impl ToString for String {
    #[inline]
    fn to_string(&self) -> String {
        self.to_owned()
    }
}

// to_owned uses clone when the type implements the Clone trait
[stable(feature = "rust1", since = "1.0.0")]
impl<T> ToOwned for T
where
    T: Clone,
{
    type Owned = T;
    fn to_owned(&self) -> T {
        self.clone()
    }

    fn clone_into(&self, target: &mut T) {
        target.clone_from(self);
    }
}

// And String implements Clone
#[stable(feature = "rust1", since = "1.0.0")]
impl Clone for String {
    fn clone(&self) -> Self {
        String { vec: self.vec.clone() }
    }

    fn clone_from(&mut self, source: &Self) {
        self.vec.clone_from(&source.vec);
    }
}

Использование времен жизни

Вы также можете решить эту проблему, используя время жизни в структуре Config, например:

struct Config<'a> {
    query: &'a String,
    filename: &'a String,
}

fn parse_config(args: &[String]) -> Config {
    let query = &args[1];
    let filename = &args[2];

    // Now config is not trying to own the Strings
    // So you can pass your references to it
    Config { query, filename }
}

Это позволяет вам реализовать код так, как вы хотите, но связывает время жизни результата Config с временем жизни args, которое в данном случае является областью действия main.

Другими словами, вы можете получить доступ только к config.filename и config.query, пока первоначальный владелец (args в main) все еще находится в области действия.

Я создал игровую площадку с рабочий пример и другой с другой областью для аргументов , чтобы вы могли видеть, как время жизни влияет на возвращение.


Я не профессионал Rust, и я сделал некоторые упрощения, чтобы сделать объяснение прямым. Если я скажу что-то не так, надеюсь, более опытный рустокан поправит меня.

1 голос
/ 10 апреля 2020

std::ops::Index возвращает ссылку на тип в контейнере, в данном случае это срез.

У вас есть несколько вариантов получения рабочего кода. Лучший из них - переписать parse_config, чтобы взять Vec.

fn parse_config(mut args: Vec<String>) -> Config {
    let filename = args.remove(2);
    let query = args.remove(1);

    Config { query, filename }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...