2017-03-06 15 views
5

私はこのコード(playground)を有する:形質の中のジェネリックメソッドが、形質オブジェクトのサイズを必要とするのはなぜですか?

use std::sync::Arc; 

pub trait Messenger : Sync + Send { 
    fn send_embed<F: FnOnce(String) -> String>(&self, u64, &str, f: F) 
     -> Option<u64> where Self: Sync + Send; 
} 

struct MyMessenger { 
    prefix: String, 
} 
impl MyMessenger { 
    fn new(s: &str) -> MyMessenger { 
     MyMessenger { prefix: s.to_owned(), } 
    } 
} 
impl Messenger for MyMessenger { 
    fn send_embed<F: FnOnce(String) -> String>(&self, channel_id: u64, text: &str, f: F) -> Option<u64> { 
     println!("Trying to send embed: chid={}, text=\"{}\"", channel_id, text); 
     None 
    } 

} 

struct Bot { 
    messenger: Arc<Messenger>, 
} 
impl Bot { 
    fn new() -> Bot { 
     Bot { 
      messenger: Arc::new(MyMessenger::new("HELLO")), 
     } 
    } 
} 

fn main() { 
    let b = Bot::new(); 
} 

Iが多型オブジェクトを作りたいと思った(形質Messengerと多型実装の1つがMyMessengerです)。私はそれをコンパイルしようとすると、しかし、私はエラーがあります:私は、私はこのケースでSizedを必要としなければならないことを発見した

error[E0038]: the trait `Messenger` cannot be made into an object 
    --> <anon>:25:5 
    | 
25 |  messenger: Arc<Messenger>, 
    |  ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Messenger` cannot be made into an object 
    | 
    = note: method `send_embed` has generic type parameters 

を、これはそれを解決しません。

fn send_embed<F: FnOnce(String) -> String>(&self, u64, &str, f: F) 
    -> Option<u64> where Self: Sized + Sync + Send; 

をそれからそれは正常にコンパイル:しかし、私は次のように私のsend_embed方法を変更した場合は

  1. は、なぜ我々はここでSizedが必要なのでしょうか?これは、traitオブジェクトからこのメソッドを使用できない場合、多態性に違反します。
  2. 私たちは、実際にはその後、Arc<Messenger>からこのメソッドを使用することはできません。

    fn main() { 
        let b = Bot::new(); 
        b.messenger.send_embed(0u64, "ABRACADABRA", |s| s); 
    } 
    

    は与える:

    error[E0277]: the trait bound `Messenger + 'static: std::marker::Sized` is not satisfied 
        --> <anon>:37:17 
        | 
    37 |  b.messenger.send_embed(0u64, "ABRACADABRA", |s| s); 
        |     ^^^^^^^^^^ the trait `std::marker::Sized` is not implemented for `Messenger + 'static` 
        | 
        = note: `Messenger + 'static` does not have a constant size known at compile-time 
    

を私はここに完全に立ち往生しています。形質でジェネリックメソッドを使って多形性を使う方法は考えられません。方法はありますか?

+3

これは、[形質の概念に関係していますオブジェクト](http://stackoverflow.com/search?q= [rust] + trait + object)と "オブジェクトの安全性"についてかなり議論されています...しかし、私は良い複製を見つけることができません詳細はこちら –

+0

[この質問](http://stackoverflow.com/questions/34758233/working-with-trait-objects-requiring-sized)への回答が役に立ちます。 – ljedrz

+0

@AleksanderFular遊び場でコードを修正しようとしましたか?あなたが言う前に私がこれを試したので、これは残念なことに私を助けませんでした。そして、あなたのコメントの後に、私はもう一度それを試しました[成功なし](https://play.rust-lang.org/?gist=4ade63728c3ad052f070c0937f0f779d&version=stable&backtrace=0)。 –

答えて

9

ダイナミックディスパッチ(traitオブジェクトによる呼び出しメソッド)は、コンパイル時にどの関数が使用されるかわからないため、vtableを使用して(つまり関数ポインタを使用して)呼び出すことによって機能します。

しかし、関数が汎用の場合、実際に使用されるFのすべてのインスタンスごとに別々に(単体化して)コンパイルする必要があります。つまり、呼び出されるクロージャのタイプごとに異なるsend_embedのコピーを作成します。すべての閉包は異なるタイプです。

これら2つのモデルは互換性がありません。異なるタイプで動作する関数ポインタを持つことはできません。

ただし、一般的なコンパイル時である代わりに、同様の形質オブジェクトを使用する方法を変更することができます。

pub trait Messenger : Sync + Send { 
    fn send_embed(&self, u64, &str, f: &Fn(String) -> String) 
     -> Option<u64> where Self: Sync + Send; 
} 

Playground)の代わりに、すべてのタイプの異なるsend_embed

をどのことができますFn(String) -> Stringである場合、これは特性オブジェクト参照を受け入れるようになりました。 (Box<Fn()>などを使用することもできます)。後者は値によってselfをとります。つまり、オブジェクトセーフではない(呼び出し側は、クロージャのselfパラメータとして渡すサイズを知らないため)FnまたはFnMutを使用し、FnOnceではなく使用する必要があります。

あなたはまだ閉鎖/ラムダ関数でsend_embedを呼び出すことができますが、それはちょうどこのように、参照することによってする必要があります:

self.messenger.send_embed(0, "abc", &|x| x); 

私が直接send_embedを呼び出す例を含めるように遊び場を更新しました参照されたクロージャー、およびBotの汎用ラッパーによる間接的なルートです。

+0

ラムダを使用する方法はありませんか?そして、もし私が 'b.messenger.send_embed(0u64、" ABRACADABRA "、&| s | s);'これは大丈夫ですか? –

+0

@VictorPolevoyできます - 私は答えをどのように表示するかを更新しました。 –

+0

ありがとうございます。最後の質問です。多相オブジェクトを作成したい場合、私はいつもそれを行うべきですか?これは通常の練習ですか、この特定の問題のための一回限りの修正ですか? –

4

vtableを実装できないため、一般的な方法はobject-safeにできません。 @ChrisEmerson's answerについて詳しく説明します。

fにジェネリックパラメータの代わりに特性オブジェクトを使用させることで、send_embedをオブジェクト特性にすることができます。関数がf: F where F: Fn(X) -> Yを受け入れる場合は、FnMut f: &mut FnMut(X) -> Yと同様に、f: &Fn(X) -> Yを受け入れるようにすることができます。 FnOnceは錆が無サイズの種類を移動することはできませんので、よりトリッキーですが、あなたはそれをボックスに試みることができる:

//   ↓ no generic   ↓~~~~~~~~~~~~~~~~~~~~~~~~~~~~ box the closure 
fn send_embed(&self, u64, &str, f: Box<FnOnce(String) -> String>) -> Option<u64> 
    where Self: Sync + Send 
{ 
    f("hello".to_string()); 
    None 
} 

b.messenger.send_embed(1, "234", Box::new(|a| a)); 
// note: does not work. 

しかし、錆1.17.0 you cannot box an FnOnce and call itのように、あなたが使用する必要がありFnBox

#![feature(fnbox)] 
use std::boxed::FnBox; 

//          ↓~~~~ 
fn send_embed(&self, u64, &str, f: Box<FnBox(String) -> String>) -> Option<u64> 
    where Self: Sync + Send 
{ 
    f("hello".to_string()); 
    None 
} 

b.messenger.send_embed(1, "234", Box::new(|a| a)); 

あなたは回避策としてクレートboxfnonceを使用することができ、不安定な機能を使用しない場合:

extern crate boxfnonce; 
use boxfnonce::BoxFnOnce; 

fn send_embed(&self, u64, &str, f: BoxFnOnce<(String,), String>) -> Option<u64> 
    where Self: Sync + Send 
{ 
    f.call("hello".to_string()); 
    None 
} 

b.messenger.send_embed(1, "234", BoxFnOnce::from(|a| a)); 
+0

私は 'FnBox'について忘れてしまいました! –

12

トレイト

  • 関連するタイプ、
  • 関連する定数、
  • 関連した機能:及び形質は

    錆では、あなたはで構成インターフェースを定義するためにtraitを使用することができます。

、あなたはどちらかの特徴を使用することができます参照やポインタの後ろに、種類などの一般的なパラメータ

  • のコンパイル時の境界として

    • を。

    しかし... 形質を直接型として使用することができます。これらの形質にはのオブジェクトセーフというラベルが付いています。

    フル機能とオブジェクトセーフな特性の両方を定義するために、単一のtraitというキーワードが存在することは、残念なことです。


    Interlude:実行時ディスパッチはどのように機能しますか?

    形質として型を使用する場合:&TraitBox<Trait>Rc<Trait>、...データポインタ、

  • 仮想ポインタ

    • :ランタイム実装は、構成ファットポインタを使用しています。

    メソッド呼び出しは、仮想テーブルに仮想ポインタを介して送出されます。以下のような形質の

    タイプXのために実装
    trait A { 
        fn one(&self) -> usize; 
        fn two(&self, other: usize) -> usize; 
    } 
    

    、仮想テーブルは(<X as A>::one, <X as A>::two)次のようになります。

    ランタイムディスパッチは、このようにすることによって行われる:データポインタおよび引数でそれを呼び出す表の右側部材をピッキング

    これは<X as A>::twoがどのように見えることを意味します

    fn x_as_a_two(this: *const(), other: usize) -> usize { 
        let x = unsafe { this as *const X as &X }; 
        x.two(other) 
    } 
    

    なぜ私はタイプとして任意の形質を使用することはできませんか?オブジェクトセーフは何ですか?

    これは技術的な制限です。

    ランタイム・ディスパッチのために実装することができない特性機能がいくつかあります:

    • 関連するタイプ、
    • 関連する定数、
    • 関連する一般的な機能、Self
    • 関連する機能は、署名の中で。
    • ...多分その他...

    この問題を通知する方法は2つあります。

    • 早期:のいずれかを使用することを拒否:
    • が遅れ、それが上記のいずれかを持っている場合型としてtraitを使用することを拒否タイプとしてtraitの上にある。今の

    、錆は早い段階で問題を知らせるために選択されます上記の機能のいずれかを使用していない特性は、コールオブジェクト安全であり、タイプとして使用することができます。

    でない特性オブジェクトセーフは、タイプとして使用することはできず、エラーがすぐにトリガされます。


    ここで何ですか?少ししわがあります

    pub trait Messenger : Sync + Send { 
        fn send_embed(&self, u64, &str, f: &FnOnce(String) -> String) 
         -> Option<u64>; 
    } 
    

    :あなたのケースでは

    は、単に実行時のために多型をメソッドのコンパイル時のポリモーフィズムを切り替えるFnOncefの外に移動する必要がが、それはここでしか借りています代わりにFnMutまたはFnを使用する必要があります。 FnMutので、次のより一般的な方法である:

    pub trait Messenger : Sync + Send { 
        fn send_embed(&self, u64, &str, f: &FnMut(String) -> String) 
         -> Option<u64>; 
    } 
    

    これは、あなたが&Messengerを使用することができますので、Messenger形質オブジェクトが安全となり、Box<Messenger>、...

  • +0

    '&FnOnce()'は呼び出すことができないと思うので、 'Fn'または' FnMut'に変更する必要があります。 –

    +0

    @ChrisEmerson:確かに良い点。参照から移動することは不可能です。 –

    関連する問題