Chuyển đổi từ Option <String> thành Option <& str>


85

Rất thường xuyên, tôi nhận được Option<String>từ một phép tính và tôi muốn sử dụng giá trị này hoặc giá trị được mã hóa cứng mặc định.

Điều này sẽ là nhỏ với một số nguyên:

let opt: Option<i32> = Some(3);
let value = opt.unwrap_or(0); // 0 being the default

Nhưng với a Stringvà a &str, trình biên dịch phàn nàn về các kiểu không khớp:

let opt: Option<String> = Some("some value".to_owned());
let value = opt.unwrap_or("default string");

Lỗi chính xác ở đây là:

error[E0308]: mismatched types
 --> src/main.rs:4:31
  |
4 |     let value = opt.unwrap_or("default string");
  |                               ^^^^^^^^^^^^^^^^
  |                               |
  |                               expected struct `std::string::String`, found reference
  |                               help: try using a conversion method: `"default string".to_string()`
  |
  = note: expected type `std::string::String`
             found type `&'static str`

Một tùy chọn là chuyển đổi lát cắt chuỗi thành Chuỗi được sở hữu, như được đề xuất bởi gỉc:

let value = opt.unwrap_or("default string".to_string());

Nhưng điều này gây ra phân bổ, điều này không mong muốn khi tôi muốn ngay lập tức chuyển đổi kết quả trở lại một lát chuỗi, như trong lệnh gọi này tới Regex::new():

let rx: Regex = Regex::new(&opt.unwrap_or("default string".to_string()));

Tôi muốn chuyển đổi Option<String>thành an Option<&str>để tránh phân bổ này.

Cách viết này là gì?

Câu trả lời:


65

Kể từ Rust 1.40, thư viện tiêu chuẩn Option::as_derefphải thực hiện điều này:

fn main() {
    let opt: Option<String> = Some("some value".to_owned());
    let value = opt.as_deref().unwrap_or("default string");
}

Nó hoạt động với tôi nhưng tôi không thể hiểu tại sao phiên bản khác của tôi đang sử dụng mapkhông hoạt động. Tôi không hiểu điều gì xảy ra với bản đầu tiên Options<String>và bản sao tham chiếu đến nó trong trường hợp as_derefbiến thể của mã. Đây là mã làm việc của tôi bằng cách sử dụng as_deref: let device_id = UsbDeviceIdentifier::VidPidSn { vid: device.vendor_id, pid: device.product_id, sn: device.serial_number.as_deref().unwrap_or("") };và lần thử đầu tiên của tôi let device_id = UsbDeviceIdentifier::VidPidSn { vid: device.vendor_id, pid: device.product_id, sn: device.serial_number.map(|s| s.as_str()).unwrap_or("") };.
Paul-Sebastian Manole

Lỗi của tôi là error[E0515]: cannot return value referencing function parameter `s`, s.as_str() returns a value referencing data owned by the current function. OK, nhưng tại sao lại as_derefhoạt động, vì nó vẫn tạo ra một tham chiếu đến bản gốc được Option<String>trả về .serial_number, vì vậy nó vẫn trỏ đến dữ liệu thuộc sở hữu của nó Option!? Có phải sự khác biệt as_derefthực hiện một chuyển đổi tại chỗ thay vì thực hiện nó trong phần thân của bao đóng, nơi nó loại bỏ nguồn của lát cắt mà nó trả về? Nếu vậy thì có cách nào để khắc phục không? Giống như trả lại một bản sao của str?
Paul-Sebastian Manole

@ Paul-SebastianManole vui lòng đọc câu trả lời hiện có cho biết cách sử dụngmap ; điều đó có giải quyết được vấn đề của bạn không?
Shepmaster

Vâng, nhưng tôi vẫn còn phân vân.
Paul-Sebastian Manole


55

Bạn có thể sử dụng as_ref()map()chuyển đổi một Option<String>thành một Option<&str>.

fn main() {
    let opt: Option<String> = Some("some value".to_owned());
    let value = opt.as_ref().map(|x| &**x).unwrap_or("default string");
}

Đầu tiên, as_ref()mặc nhiên nhận một tham chiếu vào opt, đưa ra một &Option<String>(bởi vì as_ref()take &self, tức là nó nhận một tham chiếu) và biến nó thành một Option<&String>. Sau đó, chúng tôi sử dụng mapđể chuyển đổi nó thành một Option<&str>. Đây là những gì &**xhiện: ngoài cùng bên phải *(được đánh giá đầu tiên) chỉ đơn giản là bỏ qua &String, đưa ra một Stringgiá trị. Sau đó, ngoài cùng bên trái *thực sự gọi Derefđặc điểm, bởi vì String thực hiện Deref<Target=str> , cho chúng ta một strgiá trị. Cuối cùng, &lấy địa chỉ của strlvalue, cho chúng ta một &str.

Bạn có thể đơn giản hóa điều này hơn một chút bằng cách sử dụng map_orkết hợp mapunwrap_ortrong một thao tác duy nhất:

fn main() {
    let opt: Option<String> = Some("some value".to_owned());
    let value = opt.as_ref().map_or("default string", |x| &**x);
}

Nếu &**xtrông quá kỳ diệu với bạn, bạn có thể viết String::as_str:

fn main() {
    let opt: Option<String> = Some("some value".to_owned());
    let value = opt.as_ref().map_or("default string", String::as_str);
}

hoặc String::as_ref(từ AsRefđặc điểm, ở phần dạo đầu ):

fn main() {
    let opt: Option<String> = Some("some value".to_owned());
    let value = opt.as_ref().map_or("default string", String::as_ref);
}

hoặc String::deref(mặc dù bạn cũng cần nhập Derefđặc điểm):

use std::ops::Deref;

fn main() {
    let opt: Option<String> = Some("some value".to_owned());
    let value = opt.as_ref().map_or("default string", String::deref);
}

Để một trong hai thứ này hoạt động, bạn cần phải giữ một chủ sở hữu Option<String>miễn là vẫn có sẵn Option<&str>hoặc chưa bao gói &str. Nếu điều đó quá phức tạp, bạn có thể sử dụng Cow.

use std::borrow::Cow::{Borrowed, Owned};

fn main() {
    let opt: Option<String> = Some("some value".to_owned());
    let value = opt.map_or(Borrowed("default string"), |x| Owned(x));
}

13
bikesheddy nhận xét: thay vì map(|x| &**x)bạn cũng có thể làm map(String::as_ref).
fjh

2
Điều này hoạt động tốt, mặc dù vẫn còn một chút dài dòng. Để làm rõ, loại opt.as_ref()Option<&String>, sau đó opt.as_ref().map(String::as_ref)chuyển nó &Stringđến String::as_ref, trả về một &str. Tại sao &Stringtừ Option::as_refkhông thể cưỡng chế thành an &str?
George Hilliard

1
@thirtythreeforty String::as_reftrả về một &str, không phải a &&String(xem ở đây )
fjh

@fjh Tôi chỉ nhận ra điều đó và chỉnh sửa :). Câu hỏi ép buộc của tôi vẫn còn.
George Hilliard

1
@ little-dude: Trong &**, ngoài cùng bên trái *thực sự gọi ra Derefđặc điểm, bởi vì các Stringdụng cụ Deref<Target=str>. Deref::derefAsRef::as_refcả hai đều cung cấp chuyển đổi từ tham chiếu sang tham chiếu và điều đó xảy ra là chuyển đổi từ &Stringthành &strcó sẵn với cả hai. Bạn có thể sử dụng map(String::deref)thay thế map(String::as_ref), nó cũng sẽ tương đương.
Francis Gagné

20

Một cách tốt hơn có thể là triển khai điều này một cách chung chung cho T: Deref:

use std::ops::Deref;

trait OptionDeref<T: Deref> {
    fn as_deref(&self) -> Option<&T::Target>;
}

impl<T: Deref> OptionDeref<T> for Option<T> {
    fn as_deref(&self) -> Option<&T::Target> {
        self.as_ref().map(Deref::deref)
    }
}

mà hiệu quả khái quát as_ref.


1
Đây là một chức năng tiện ích tuyệt vời. Tôi ước nó là một phần của thư viện chuẩn dưới dạng một hàm trên Option <T>!
Bill Fraser

1
Cảm ơn! Điều này phù hợp với tôi - nếu tôi bao gồm mã này, thì opt.as_deref()thực sự là một Option<&str>.
rspeer

7

Mặc dù tôi yêu câu trả lời Veedrac của (tôi đã sử dụng nó), nếu bạn cần nó tại chỉ là một điểm và bạn muốn một cái gì đó là biểu cảm, bạn có thể sử dụng as_ref(), mapString::as_strchuỗi:

let opt: Option<String> = Some("some value".to_string());

assert_eq!(Some("some value"), opt.as_ref().map(String::as_str));

1

Đây là một cách bạn có thể làm. Hãy nhớ rằng bạn phải giữ nguyên bản gốc Stringxung quanh, nếu không thì đó sẽ &strlà một lát cắt thành gì?

let opt = Some(String::from("test")); // kept around

let unwrapped: &str = match opt.as_ref() {
  Some(s) => s, // deref coercion
  None => "default",
};

vở kịch


2
Điều đó không biến nó thành một Option <& str>.
rspeer
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.