Сортировать вектор структур по строковому полю - PullRequest
1 голос
/ 13 мая 2019

У меня трудности с, казалось бы, тривиальной сортировкой по строковому полю. Repro ниже:

struct Dummy {
    x: String,
    y: i8
}

fn main() {
    let mut dummies: Vec<Dummy> = Vec::new();
    dummies.push(Dummy { x: "a".to_string(), y: 1 });
    dummies.push(Dummy { x: "b".to_string(), y: 2 });

    dummies.sort_by_key(|d| d.x); // error[E0507]: cannot move out of borrowed content
    dummies.sort_by_key(|d| d.y); // This is fine
}

Может кто-нибудь объяснить, что именно не так и как это исправить? Описание ошибки имеет смысл, но оно еще не щелкнуло.

Ответы [ 3 ]

3 голосов
/ 13 мая 2019

Сначала давайте посмотрим на ваше исходное сообщение об ошибке, затем пройдемся по нескольким исправлениям и попытаемся все понять.

В замыкании, которое вы используете в dummies.sort_by_key(|d| d.x);, d - этоссылка на экземпляр Dummy.Однако поле доступа d.x является самим String.Если вы хотите вернуть это String, вам придется передать право собственности на него тому, что называется закрытием.Но так как d был просто ссылкой, вы не можете передать право собственности на его данные.

Одно простое решение - просто клонировать строку как dummies.sort_by_key(|d| d.x.clone());.Это делает копию строки перед возвратом в замыкание (это решение Андры).Это прекрасно работает, но если проблема заключается в производительности или использовании памяти, мы можем избежать клона.

Идея в том, что использование строки в качестве ключа расточительно.Действительно, все, что нам нужно знать, это то, какая из двух строк меньше.Если мы используем строку в качестве ключа, то каждый раз, когда функция сортировки должна сравнивать два Dummy s, она вызывает функцию ключа для каждого и строки передаются (очень короткой) функции, которая просто сравнивает их.Если бы мы провели сравнение в том же контексте, что и заимствование, мы могли бы просто передать результат сравнения, а не строки.

Решением является sort_by метод на ломтиках.Это позволяет нам взять ссылки на два Dummy s и решить, если один меньше другого.Например, мы можем использовать его как dummies.sort_by(|d1, d2| d1.x.cmp(&d2.x)); (полный пример здесь)

Приложение

Почему мы не можем использовать sort_by_key без клонирования String s??Конечно, должен быть какой-то умный способ использовать кусочки строк и время жизни, чтобы сделать это.

Давайте посмотрим на сигнатуру функции sort_by_key.

pub fn sort_by_key<K, F>(&mut self, f: F) where
    F: FnMut(&T) -> K,
    K: Ord, 

Интересная часть этой функции не в том, что там, а в том, чего нет.Параметр типа K не зависит от времени жизни ссылки, переданной в f.

При сортировке среза функция ключа повторно вызывается со ссылкой на экземпляр Dummy.Поскольку срез сортируется немного между каждым вызовом, время жизни ссылки должно быть очень коротким.Если бы он был длиннее, он стал бы недействительным при следующем перемещении элементов среза.Однако K не может зависеть от этого времени жизни.Это означает, что, какой бы ни была наша ключевая функция, она не может вернуть ничего, что зависит от текущего местоположения Dummy (например, фрагмент строки, ссылка или любая другая умная конструкция 1 ).

Однако мы можем сделать K зависимым от времени жизни того, что ему передано.Идея здесь в том, что называется Границы черт высшего ранга .В настоящее время они работают только с временами жизни (хотя теоретически они могут быть распространены на все параметры типа).Мы могли бы установить другой метод среза с подписью

fn sort_by_key_hrtb<T, F, K>(slice: &mut [T], f: F)
where
    F: Fn(&T) -> &K,
    K: Ord,

Почему это работает?В F: Fn(&T) -> &K, время жизни выходной ссылки неявно равно (или больше) времени жизни входной ссылки.Desugared, это F: for<'a> Fn(&'a T) -> &'a K,, который говорит, что f должен иметь возможность взять ссылку с любым временем жизни 'a и вернуть ссылку с временем жизни (большим или равным) 'a.Теперь у нас есть метод, который работает именно так, как вы хотели его использовать (за исключением надоедливых & 2 ). (ссылка на игровую площадку)


  1. На самом деле есть одна (небезопасная) умная конструкция, которая, вероятно, работает, но я не проверял ее.Вы можете использовать обертку вокруг необработанного указателя на String, а затем impl Ord для этой обертки, чтобы она разыменовывала указатель для выполнения сравнения. 3 Тип возвращаемого значения для ключевой функции будет *const String, поэтому нам не нужно никаких жизней.Это по своей сути небезопасно, и я определенно не рекомендую это.(Вероятно) рабочий пример: здесь .

  2. Единственная причина, по которой нам нужно использовать &mut dummies, заключается в том, что sort_by_key_hrtb на самом деле не является методом среза.Если бы это было так, dummies было бы автоматически заимствовано и разыменовано в срез, поэтому мы могли бы вызывать такую ​​функцию, как dummies.sort_by_key_hrtb(|d| &d.x);.

  3. Почему обертка вместо простого указателя?*const T реализует Ord, но делает это путем сравнения адресов, а не базового значения (если оно есть), что здесь не то, что мы хотим.

1 голос
/ 13 мая 2019

Функция sort_by_key становится владельцем ключа: pub fn sort_by_key<K, F>(&mut self, f: F) (https://doc.rust -lang.org / std / vec / struct.Vec.html # method.sort_by_key )

Вот почему вы получаете: https://doc.rust -lang.org / error-index.html # E0507

Простым исправлением будет сохранение ссылки на вашу структуру, так что sort_by_keyне будет владеть вашим ключом.

Тогда вам нужно иметь время жизни до указанного значения, чтобы ее можно было отбросить, когда ваша структура исчезнет.

struct Dummy<'a> {
    x: &'a str,
    y: i8,
}

fn main() {
    let mut dummies: Vec<Dummy> = Vec::new();
    dummies.push(Dummy { x: "a", y: 1 });
    dummies.push(Dummy { x: "b", y: 2 });

    dummies.sort_by_key(|d| d.x);
    dummies.sort_by_key(|d| d.y);
}
1 голос
/ 13 мая 2019

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

Это отлично работает

struct Dummy {
    x: String,
    y: i8
}

fn main() {
    let mut dummies: Vec<Dummy> = Vec::new();
    dummies.push(Dummy { x: "a".to_string(), y: 1 });
    dummies.push(Dummy { x: "b".to_string(), y: 2 });

    dummies.sort_by_key(|d| d.x.clone()); // Clone the string
    dummies.sort_by_key(|d| d.y); // This is fine
}

Поведение может выглядеть примерно так

struct Dummy {
    x: String,
    y: i8
}

fn main() {
    let mut dummies: Vec<Dummy> = Vec::new();
    dummies.push(Dummy { x: "a".to_string(), y: 1 });
    dummies.push(Dummy { x: "b".to_string(), y: 2 });
    let mut temp = Dummy{ x: "c".to_string(), y: 3 };
    temp.x = dummies[0].x; // Error[E0507]: cannot move out of borrowed content
}

Использование clone() как в примере выше

struct Dummy {
    x: String,
    y: i8
}

fn main() {
    let mut dummies: Vec<Dummy> = Vec::new();
    dummies.push(Dummy { x: "a".to_string(), y: 1 });
    dummies.push(Dummy { x: "b".to_string(), y: 2 });
    let mut temp = Dummy{ x: "c".to_string(), y: 3 };
    temp.x = dummies[0].x.clone(); // Fine
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...