コピータイプの借入特性の反対ですか?

Aug 18 2020

Borrow所有されている型または参照の両方を受け入れる関数を定義するために使用される特性(例:Tまたは)を見てきました&T。次にborrow()、関数内でメソッドが呼び出され、を取得し&Tます。

反対(受け入れすなわち機能ができますいくつかの特性がありT&Tと取得Tのための)Copyのタイプは?

この例の例:

use std::borrow::Borrow;

fn foo<T: Borrow<u32>>(value: T) -> u32 {
    *value.borrow()
}

fn main() {
    println!("{}", foo(&5));
    println!("{}", foo(5));
}

これはborrow()参照を取得するために呼び出され、参照はすぐに逆参照されます。

T渡された場合は値をコピーし、&T指定された場合は逆参照する別の実装はありますか?それとも、上記はこの種のことを書く慣用的な方法ですか?

回答

5 trentcl Aug 18 2020 at 14:09

の逆特性は実際にはありません。これはBorrow、同じように関数の境界として実際には有用ではないためBorrowです。その理由は所有権に関係しています。

「逆Borrow」がより役に立たないのはなぜBorrowですか?

参照が必要な関数

引数を参照するだけでよい関数について考えてみましょう。

fn puts(arg: &str) {
    println!("{}", arg);
}

データの所有権を取得する必要がないStringため、ここで受け入れるのはばかげていますputsが、受け入れる&strとは、呼び出し元に必要以上にデータを保持するように強制する場合があることを意味します。

{
    let output = create_some_string();
    output.push_str(some_other_string);
    puts(&output);
    // do some other stuff but never use `output` again
} // `output` isn't dropped until here

問題は、にoutput渡された後は必要なくputs、呼び出し元はこれを知っていputsますが、参照が必要なためoutput、ブロックが終了するまで存続する必要があります。もちろん、あなたはいつもより多くのブロック、時には追加することで、呼び出し側でこの問題を解決することができletますが、putsまた、発信者ができるように、一般的な行うことができ委任クリーンアップの責任をoutput

fn puts<T: Borrow<str>>(arg: T) {
    println!("{}", arg.borrow());
}

T: Borrowforputsを受け入れると、呼び出し元は引数を保持するか、関数に移動するかを柔軟に決定できます。

所有する値を必要とする関数

ここで、実際に所有権を取得する必要がある関数の場合を考えてみましょう。

struct Wrapper(String);
fn wrap(arg: String) -> Wrapper {
    Wrapper(arg)
}

この場合、それを要求しなければならない&strので、受け入れることはばかげているwrapでしょうto_owned()。呼び出し元がString使用しなくなったを持っている場合、関数に移動された可能性のあるデータを不必要にコピーします。この場合、String呼び出し側がクローンを作成するか既存のを渡すかを決定できるため、受け入れはより柔軟なオプションStringです。「逆Borrow」の特性を持ってarg: Stringいても、まだ提供されていない柔軟性は追加されません。

しかし、String文字列のいくつかの種類があるため、常に最も人間工学的な引数ではありません:&strCow<str>Box<str>...我々は作ることができwrap、それは、変換することができ何でも受け入れると言って、もう少し人間工学に基づいたintoAをString

fn wrap<T: Into<String>>(arg: T) -> Wrapper {
    Wrapper(arg.into())
}

これは、リテラルwrap("hello, world")を呼び出さなくても、同じように呼び出すことができることを意味します.to_owned()。これは実際には柔軟性のある勝利ではありません-発信者.into()は一般性を失うことなくいつでも代わりに電話をかけることができます-しかしそれは人間工学的な勝利です。

何についてCopyのタイプ?

さて、あなたはCopyタイプについて尋ねました。ほとんどの場合、上記の議論は依然として適用されます。のようにputs、だけが必要な関数を作成している場合は&A、使用T: Borrow<A>する方が呼び出し元にとってより柔軟な場合があります。wrapそのような関数全体が必要な場合はA、受け入れるだけの方が柔軟性がありますA。しかし、Copyタイプの場合、受け入れることの人間工学的利点T: Into<A>はそれほど明確ではありません。

  • 型推論、とジェネリック医薬品の混乱がそれらを使用しているため、整数型の場合、通常はそれが作るより少ない人間工学に基づいたリテラルを使用します。型に明示的に注釈を付ける必要が生じる可能性があります。
  • &u32は実装されていないのでInto<u32>、その特定のトリックはとにかくここでは機能しません。
  • Copy型は所有値としてすぐに利用できるため、そもそも参照によってそれらを使用することはあまり一般的ではありません。
  • 最後に、a&AAwhenに変換するのA: Copyは、*;を追加するだけです。そのステップをスキップできることは、ほとんどの場合、ジェネリックを使用することの追加された複雑さを相殺するのに十分な説得力のある勝利ではないでしょう。

結論として、fooほぼ確実value: u32に、その値を取得する方法を発信者に受け入れて決定させる必要があります。

も参照してください

  • メソッドが値の所有権を必要とする場合、値渡しまたは参照渡しのどちらが一般的ですか?
2 Sunreef Aug 18 2020 at 11:00

持っている関数では、u32またはとして借用できるタイプのみを使用できますu32

2番目のテンプレート引数を使用して、関数をより汎用的にすることができます。

fn foo<T: Copy, N: Borrow<T>>(value: N) -> T {
    *value.borrow()
}

ただし、これは部分的な解決策にすぎません。正しく機能するために型の注釈が必要になる場合があるためです。

たとえば、次のようにすると、すぐに使用できますusize

let v = 0usize;
println!("{}", foo(v));

何ら問題は推測にコンパイラのため、ここではありませんfoo(v)ですusize

ただし、を実行しようとするfoo(&v)と、コンパイラは、異なるタイプに対していくつかの特性を実装する可能性があるTため、正しい出力タイプが見つからないと文句を言います。出力として使用するものを明示的に指定する必要があります。&TBorrow

let output: usize = foo(&v);