Option <String>からOption <&str>への変換


85

非常に頻繁に私は Option<String>計算からありますが、この値またはデフォルトのハードコードされた値のいずれかを使用したいと思います。

これは整数では簡単です。

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

しかし、aStringとaを使用すると&str、コンパイラは型の不一致について文句を言います。

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

ここでの正確なエラーは次のとおりです。

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`

1つのオプションは、rustcによって提案されているように、文字列スライスを所有されている文字列に変換することです。

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

しかし、これにより割り当てが発生します。これは、次の呼び出しのように、結果をすぐに文字列スライスに変換する場合には望ましくありませんRegex::new()

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

私はむしろ変換したい Option<String>Option<&str>この割り当てを回避するために、をにます。

これを書くための理想的な方法は何ですか?

回答:


66

Rust 1.40の時点で、標準ライブラリはOption::as_derefこれを行う必要があります。

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

それは私にとっては機能しますが、他のバージョンを使用mapしても機能しなかった理由がわかりません。コードのバリアントのOptions<String>場合、最初のコピーとそれを参照するコピーがどうなるかわかりませんas_deref。これがas_dereflet device_id = UsbDeviceIdentifier::VidPidSn { vid: device.vendor_id, pid: device.product_id, sn: device.serial_number.as_deref().unwrap_or("") };と私の最初の試みを使用した私の作業コードlet device_id = UsbDeviceIdentifier::VidPidSn { vid: device.vendor_id, pid: device.product_id, sn: device.serial_number.map(|s| s.as_str()).unwrap_or("") };です。
ポール-セバスチャンマノール

私のエラーはerror[E0515]: cannot return value referencing function parameter `s`s.as_str() returns a value referencing data owned by the current functionです。OKですが、なぜ機能するのですか。それでも、によって返されas_derefた元の参照が作成Option<String>され.serial_number、それが所有するデータを指しているからOptionです!?as_derefクロージャーの本体で変換を行うのとは対照的に、変換を行う違いはありますか?それは、それが返すスライスのソースを破棄しますか?もしそうなら、それを修正する方法はありますか?strのコピーを返すように?
ポール-セバスチャンマノール

@ Paul-SebastianManoleの使用方法を示す既存の回答mapをお読みください。それはあなたの問題を解決しますか?
シェップマスター

ええ、でも私はまだ戸惑っています。
ポール-セバスチャンマノール


55

あなたは使用することができますas_ref()し、map()変換することOption<String>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");
}

まず、as_ref()暗黙的に参照を取るopt与え、&Option<String>(のでas_ref()とり&self、それは参照を受け、すなわち)、およびに変換しますOption<&String>。次に、を使用mapしてそれをに変換しOption<&str>ます。実行内容&**xは次のとおりです。右端*(最初に評価される)は単にを逆参照し&StringString左辺値を与えます。次に、左端が*実際にDerefトレイトを呼び出します。これString Deref<Target=str>、を実装して、str左辺値を与えるためです。最後に、&str左辺値のアドレスを取り、を与え&strます。

を使用して1回の操作でmap_or組み合わせるmapunwrap_or、これをもう少し単純化できます。

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

&**x魔法のように見える場合は、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);
}

またはString::as_refプレリュードにAsRefある特性から):

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

またはString::derefDerefトレイトもインポートする必要がありますが):

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

これらのいずれかが機能するためにはOption<String>Option<&str>またはラップさ&strれていないものが利用可能である必要がある限り、所有者を維持する必要があります。それが複雑すぎる場合は、を使用できます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
バイクシェディコメント:map(|x| &**x)あなたの代わりに行うこともできますmap(String::as_ref)
fjh 2015

2
これはうまく機能しますが、まだ少し冗長です。明確にするために、のタイプはでopt.as_ref()ありOption<&String>、次にopt.as_ref().map(String::as_ref)これ&StringString::as_ref、に渡します&str。これは。を返します。&StringからOption::as_refを強制できないのはなぜ&strですか?
ジョージヒリアード2015

1
@thirtythreefortyString::as_refは、&strではなく、を返します&&Stringここを参照)
fjh 2015

@fjh私はそれを理解して編集しました:)。私の強制の質問は残っています。
ジョージヒリアード2015

1
@ little-dude:では&**、を実装しているため、左端が*実際にDerefトレイトを呼び出します。そして、の両方が参照する参照変換を提供し、そしてそれから変換ことが起こるには、両方で利用可能です。の代わりに使用できますが、同等です。StringDeref<Target=str>Deref::derefAsRef::as_ref&String&strmap(String::deref)map(String::as_ref)
フランシス・ガニエ

20

より良い方法は、これを一般的に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)
    }
}

これは効果的に一般化しas_refます。


1
これは素晴らしいユーティリティ機能です。Option <T>の関数として標準ライブラリの一部だったらいいのにと思います。
ビルフレイザー

1
ありがとう!これは私にとってはうまくいきます-このコードを含めるopt.as_deref()と、確かにOption<&str>です。
rspeer 2016

7

私はVeedracの答えが大好きです(私はそれを使用しました)が、ある時点でそれが必要で、表現力豊かなものが必要な場合は、使用as_ref()mapString::as_strチェーンすることができます:

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

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

1

これがあなたがそれをすることができる1つの方法です。オリジナルを維持する必要あること覚えておいてくださいString。そうしないと、何に&strスライスされますか?

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

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

ベビーサークル


2
それはそれをOption <&str>に変えませんでした。
rspeer 2016
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.