2015-09-16 13 views
5

Tôi có mã đơn giản sau, trong đó cấu trúc A chứa một thuộc tính nhất định. Tôi muốn tạo các phiên bản mới của A từ phiên bản hiện tại của thuộc tính đó, nhưng làm cách nào để làm cho tuổi thọ của giá trị mới của thuộc tính cuối cùng qua cuộc gọi hàm?Trả lại thứ gì đó được phân bổ trên ngăn xếp

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); 
} 

Đoạn mã trên tạo

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| | } 
    | |_^ 

Trả lời

12

Câu hỏi này chắc chắn hầu hết đã được trả lời trước đó, nhưng tôi không đóng cửa nó như là một bản sao vì mã ở đây là hơi khác nhau và tôi nghĩ rằng điều quan trọng là.

Lưu ý cách bạn định nghĩa chức năng của bạn:

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

Nó nói rằng nó sẽ trả về một giá trị kiểu A mà bên trong sống chính xác miễn là chuỗi cung cấp. Tuy nhiên, các cơ quan chức năng vi phạm tuyên bố này:

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

Ở đây bạn xây dựng một mớiString thu được từ orig, phải mất một lát nó và cố gắng gửi lại cho bên A. Tuy nhiên, tuổi thọ của biến tiềm ẩn được tạo cho orig.to_string() + "suffix" nhỏ hơn so với tuổi thọ của tham số đầu vào. Do đó, chương trình của bạn bị từ chối.

Một cách thực tế khác để xem xét điều này là xem xét chuỗi được tạo bởi to_string() và nối phải sống ở đâu đó. Tuy nhiên, bạn chỉ trả lại một phần của nó. Do đó khi hàm thoát, chuỗi bị hủy và lát được trả về sẽ không hợp lệ. Đây chính xác là tình huống mà Rust ngăn cản.

Để khắc phục điều này bạn có thể lưu trữ một String bên A:

pub struct A { 
    some_attr: String 
} 

hoặc bạn có thể sử dụng std::borrow::Cow để lưu trữ hoặc một lát hoặc một chuỗi sở hữu:

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

Trong trường hợp cuối cùng của bạn chức năng có thể trông giống như sau:

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

Lưu ý rằng vì bạn xây dựng chuỗi bên trong hàm, nó được biểu diễn dưới dạng biến thể thuộc sở hữu của Cow và vì vậy bạn có thể sử dụng tham số trọn đời 'static cho giá trị kết quả. Việc gắn nó vào orig cũng có thể nhưng không có lý do để làm như vậy.

Với Cow nó cũng có thể tạo ra giá trị của A trực tiếp ra khỏi lát mà không cần phân bổ:

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

Đây là thông số tuổi thọ của A sẽ được gắn (thông qua sự bỏ bớt đời) để tuổi thọ của chuỗi đầu vào lát. Trong trường hợp này, biến thể được vay của Cow được sử dụng và không được phân bổ.Cũng cần lưu ý rằng tốt hơn là sử dụng to_owned() hoặc into() để chuyển đổi các lát chuỗi thành String vì các phương pháp này không yêu cầu mã định dạng để chạy và vì vậy chúng hiệu quả hơn.

làm cách nào bạn có thể trả về A trong suốt đời 'static khi bạn đang tạo nó khi đang di chuyển? Không chắc chắn "biến thể sở hữu của Cow" có nghĩa là gì và tại sao có thể làm cho 'static trở thành có thể.

Dưới đây là định nghĩa của Cow:

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

Có vẻ phức tạp nhưng nó là trong thực tế đơn giản. Một thể hiện của Cow có thể chứa tham chiếu đến một số loại B hoặc giá trị thuộc sở hữu có thể được lấy từ B qua đặc điểm ToOwned. Bởi vì str thực hiện ToOwned nơi Owned liên quan đến loại tương đương với String (viết như ToOwned<Owned = String>, khi enum này chuyên cho str, nó trông như thế này:

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

Do đó, Cow<str> có thể đại diện cho một trong hai một lát chuỗi hoặc một chuỗi sở hữu - và trong khi Cow thực sự cung cấp các phương thức cho chức năng sao chép trên ghi, nó cũng giống như thường được sử dụng để giữ một giá trị có thể mượn hoặc được sở hữu để tránh phân bổ thêm. với rebo đơn giản rrowing: nếu xCow<str>, thì &*x&str, bất kể nội dung bên trong của x - một cách tự nhiên, bạn có thể lấy một phần của cả hai biến thể Cow.

Bạn có thể thấy rằng biến thể Cow::Owned không chứa bất kỳ tham chiếu nào bên trong nó, chỉ String. Do đó, khi giá trị của Cow được tạo bằng cách sử dụng biến thể Owned, bạn có thể chọn bất kỳ thời gian nào bạn muốn (hãy nhớ, tham số thời gian giống như thông số loại chung; đặc biệt, đó là người gọi sẽ chọn chúng) - có không có hạn chế về nó. Vì vậy, nên chọn 'static là thời gian tốt nhất có thể.

orig.to_owned xóa quyền sở hữu khỏi bất kỳ ai gọi chức năng này? Nghe có vẻ như nó sẽ bất tiện.

Phương pháp to_owned() thuộc về ToOwned đặc điểm:

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

đặc điểm này được thực hiện bởi str với Owned bằng String. Phương thức to_owned() trả về biến thể thuộc sở hữu của bất kỳ giá trị nào mà nó được gọi. Trong trường hợp cụ thể này, nó tạo ra một String trong số &str, sao chép hiệu quả nội dung của đoạn chuỗi thành phân bổ mới. Do đó không, to_owned() không ngụ ý chuyển quyền sở hữu, nó giống như nó ngụ ý một bản sao "thông minh".

Theo như tôi có thể nói Chuỗi thực hiện Into<Vec<u8>> nhưng không str, vậy làm thế nào chúng ta có thể gọi into() trong ví dụ 2?

Đặc điểm Into rất linh hoạt và được triển khai cho nhiều loại trong thư viện chuẩn. Into thường được triển khai thông qua các đặc điểm From: nếu T: From<U>, sau đó U: Into<T>. Có hai triển khai quan trọng của From trong thư viện chuẩn:

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

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

Những triển khai rất đơn giản - họ chỉ trở Cow::Borrowed(value) nếu value&strCow::Owned(value) nếu valueString.

Điều này có nghĩa là &'a strString triển khai Into<Cow<'a, str>> và để chúng có thể được chuyển đổi thành Cow với phương pháp into(). Đó là chính xác những gì xảy ra trong ví dụ của tôi - Tôi đang sử dụng into() để chuyển đổi String hoặc &str thành Cow<str>. Nếu không có chuyển đổi rõ ràng này, bạn sẽ gặp lỗi về các loại không khớp.

+0

Cảm ơn, nó hoạt động! Nhưng có rất nhiều điều tôi không hiểu ở đây, làm thế nào bạn có thể trả lại một 'A' của cuộc đời 'tĩnh' khi bạn đang tạo ra nó trên bay? Bạn không chắc chắn "biến thể sở hữu" của "Bò" có nghĩa là gì và tại sao điều đó làm cho ''tĩnh' có thể xảy ra. Chúng ta không bao giờ sửa đổi 'some_attr' tại bất kỳ thời điểm nào - không phải thường là điểm" copy on write ", để cho phép viết? Liệu 'orig.to_owned' có xóa quyền sở hữu khỏi bất kỳ ai đang gọi chức năng này không? Nghe có vẻ như nó sẽ bất tiện. Theo như tôi có thể nói 'String' thực hiện' vào > 'nhưng không' str', vậy làm thế nào chúng ta có thể gọi 'vào()' trong ví dụ thứ 2? – wrongusername

+0

Whoa, đó là rất nhiều câu hỏi! Tôi đã cập nhật câu trả lời của tôi, hy vọng nó sẽ hữu ích :) –

Các vấn đề liên quan