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 x
là Cow<str>
, thì &*x
là &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ể.
Có 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
là &str
và Cow::Owned(value)
nếu value
là String
.
Điều này có nghĩa là &'a str
và String
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.
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
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 :) –