2015-09-16 3 views
5

У меня есть следующий упрощенный код, где struct A содержит определенный атрибут. Я хотел бы создать новые экземпляры A из существующей версии этого атрибута, но как мне сделать время жизни нового значения атрибута последним после вызова функции?Вернуть то, что выделено в стеке

pub struct A<'a> { 
    some_attr: &'a str, 
} 

impl<'a> A<'a> { 
    fn combine(orig: &'a str) -> A<'a> { 
     let attr = &*(orig.to_string() + "suffix"); 
     A { some_attr: attr } 
    } 
} 

fn main() { 
    println!("{}", A::combine("blah").some_attr); 
} 

Приведенный выше код производит

error[E0597]: borrowed value does not live long enough 
--> src/main.rs:7:22 
    | 
7 |   let attr = &*(orig.to_string() + "suffix"); 
    |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ does not live long enough 
8 |   A { some_attr: attr } 
9 |  } 
    |  - temporary value only lives until here 
    | 
note: borrowed value must be valid for the lifetime 'a as defined on the impl at 5:1... 
--> src/main.rs:5:1 
    | 
5 |/impl<'a> A<'a> { 
6 | |  fn combine(orig: &'a str) -> A<'a> { 
7 | |   let attr = &*(orig.to_string() + "suffix"); 
8 | |   A { some_attr: attr } 
9 | |  } 
10| | } 
    | |_^ 

ответ

12

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

Обратите внимание, как вы определили свою функцию:

fn combine(orig: &'a str) -> A<'a> 

Он говорит, что он будет возвращать значение типа A, чьи внутренности живут ровно столько, сколько предоставленной строкой. Тем не менее, тело функции нарушает это заявление:

let attr = &*(orig.to_string() + "suffix"); 
A { 
    some_attr: attr 
} 

Здесь вы построить новыйString, полученный из orig, возьмите кусочек него и попытаться вернуть его в A. Однако время жизни неявной переменной, созданной для orig.to_string() + "suffix", строго меньше времени жизни входного параметра. Поэтому ваша программа отклоняется.

Другой, более практичный способ взглянуть на это - считать, что строка, созданная to_string(), и конкатенация должна где-то жить. Однако вы возвращаете только заимствованный кусочек. Таким образом, когда функция выходит, строка уничтожается, и возвращенный фрагмент становится недействительным. Это именно та ситуация, которую предотвращает ржавчина.

Чтобы преодолеть это, вы можете хранить String внутри A:

pub struct A { 
    some_attr: String 
} 

или вы можете использовать std::borrow::Cow для хранения либо кусочка или принадлежащей строки:

pub struct A<'a> { 
    some_attr: Cow<'a, str> 
} 

В последнем случае ваши функция может выглядеть так:

fn combine(orig: &str) -> A<'static> { 
    let attr = orig.to_owned() + "suffix"; 
    A { 
     some_attr: attr.into() 
    } 
} 

Обратите внимание, что, поскольку вы строите строку внутри функции, она представляется как вариант, принадлежащий Cow, и поэтому вы можете использовать параметр времени жизни для результирующего значения. Также возможно привязать его к orig, но нет оснований для этого.

С Cow также можно создавать значения A непосредственно из ломтиков без выделения:

fn new(orig: &str) -> A { 
    A { some_attr: orig.into() } 
} 

Здесь параметр жизни A будет связан (через всю жизнь элизию) к жизни входной строки кусочек. В этом случае используется заемный вариант Cow, и выделение не производится.

Также обратите внимание, что лучше использовать to_owned() или into() для преобразования строки ломтиков String потому, что эти методы не требуют форматирование коды для запуска и поэтому они являются более эффективными.

Как вы можете вернуть A срока службы 'static, когда вы его создаете на лету? Не знаете, какой «принадлежащий вариант Cow» означает и почему это делает возможным 'static.

Вот определение Cow:

pub enum Cow<'a, B> where B: 'a + ToOwned + ?Sized { 
    Borrowed(&'a B), 
    Owned(B::Owned), 
} 

Это выглядит сложным, но это на самом деле просто. Экземпляр Cow может содержать ссылку на какой-либо тип B или принадлежащее ему значение, которое может быть получено из B с помощью признака ToOwned. Поскольку str реализует ToOwned где Owned соответствующий тип равен String (записывается как ToOwned<Owned = String>, когда это перечисление специализируется на str, это выглядит следующим образом:

pub enum Cow<'a, str> { 
    Borrowed(&'a str), 
    Owned(String) 
} 

Поэтому Cow<str> может представлять собой либо строку срез или принадлежащую строку - и в то время как Cow действительно предоставляет методы для функций клонирования на запись, он так же часто используется для хранения значения, которое может быть заимствовано или принадлежит во избежание дополнительных ассигнований. Поскольку Cow<'a, B> реализует Deref<Target = B>, вы можете получить &B от Cow<'a, B> с простой ребой rrowing: если x - Cow<str>, то &*x - &str, независимо от того, что содержится внутри x - естественно, вы можете получить кусочек из обоих вариантов Cow.

Вы можете видеть, что вариант Cow::Owned не содержит ссылок внутри него, только String. Поэтому, когда значение Cow создается с использованием варианта Owned, вы можете выбрать любое время жизни, которое вы хотите (помните, что параметры времени жизни очень похожи на параметры типового типа, в частности, это тот, кто выбирает их) - есть никаких ограничений на это. Поэтому имеет смысл выбрать 'static как можно большую продолжительность жизни.

orig.to_owned Удалить собственность от тех, кто вызывает эту функцию? Похоже, это было бы неудобно.

Метод to_owned() принадлежит ToOwned признаку:

pub trait ToOwned { 
    type Owned: Borrow<Self>; 
    fn to_owned(&self) -> Self::Owned; 
} 

Эта черта реализуется str с Owned равным String. Метод to_owned() возвращает собственный вариант любого значения, на которое он вызывается. В этом конкретном случае он создает String из &str, эффективно копируя содержимое среза строки в новое распределение. Поэтому no, to_owned() не подразумевает передачу прав собственности, это скорее похоже на «умный» клон.

Насколько я могу сказать Струнный реализует Into<Vec<u8>> но не str, так как мы можем назвать into() в 2-ом примере?

Характеристика Into очень универсальна и реализована для множества типов в стандартной библиотеке. Into обычно реализуется через черту From: если T: From<U>, то U: Into<T>. Есть два важных реализаций From в стандартной библиотеке:

impl<'a> From<&'a str> for Cow<'a, str> 

impl<'a> From<String> for Cow<'a, str> 

Эти реализации очень просты - они просто возвращают Cow::Borrowed(value) если value является &str и Cow::Owned(value) если value является String.

Это означает, что &'a str и String осуществить Into<Cow<'a, str>>, и поэтому они могут быть преобразованы в Cow с into() методом. Это именно то, что происходит в моем примере - я использую into() для преобразования String или &str в Cow<str>. Без этого явного преобразования вы получите ошибку о несогласованных типах.

+0

Спасибо, это работает! Но здесь я не понимаю, как вы можете вернуть «A' жизни« статический », когда вы его создаете« на лету »? Не уверен, что означает «собственный вариант« Корова »и почему это делает« статическим »возможным. Мы никогда не модифицируем 'some_attr' в любой момент - не так ли, как правило,« копировать при записи », чтобы разрешить запись? «Orig.to_owned» удаляет право собственности у того, кто вызывает эту функцию? Похоже, это было бы неудобно. Насколько я могу сказать, 'String' реализует' Into > ', но не' str', так как мы можем называть 'in()' во втором примере? – wrongusername

+0

Эй, это много вопросов! Я обновил свой ответ, надеюсь, это было бы полезно :) –