Rust: `impl Trait`の追跡の問題(RFC 1522、RFC 1951、RFC 2071)

作成日 2016ĺš´06月27日  Âˇ  417コメント  Âˇ  ソース: rust-lang/rust

新しい追跡の問題= https://github.com/rust-lang/rust/issues/63066

実施状況

RFC 1522で指定されますが、まだ作業が必要なリビジョンがあります。

RFC

impl特性に関する多くのRFCがあり、そのすべてがこの中央追跡の問題によって追跡されています。

  • https://github.com/rust-lang/rfcs/pull/1522

    • オリジナルは、固有の機能のリターン位置にあるimplTraitのみをカバーしていました

  • https://github.com/rust-lang/rfcs/pull/1951

    • 特定の構文設計に落ち着き、いくつか/任意の提案やその他に関する質問を解決します。

    • implTraitのスコープで考慮されるタイプとライフタイムパラメーターに関する質問を解決します。

    • 引数の位置にimplTraitを追加します。

  • https://github.com/rust-lang/rfcs/pull/2071

    • モジュールと実装でabstract typeという名前



      • [x]構文を完成させる(https://github.com/rust-lang/rfcs/pull/2515)


      • [] RFCがマージされた場合、RFC https://github.com/rust-lang/rfcs/pull/2289を更新して、その構文に一致させます。



    • let 、 const 、およびstatic位置でのimplトレイトの使用

  • https://github.com/rust-lang/rfcs/pull/2250

    • impl Traitとdyn Traitの構文を複数の境界で完成させる

未解決の質問

実装により、いくつかの興味深い質問も提起されました。

  • [x]型を解析するときのimplキーワードの優先順位は何ですか? ディスカッション: 1
  • [] fnタイプまたは括弧の砂糖で->後にimpl Traitを許可する必要がありますか? 45994
  • []自動安全リークを可能にするために、すべての機能にDAGを課す必要がありますか、それともある種の延期を使用できますか。 ディスカッション: 1

    • 現在のセマンティクス:DAG。

  • [x] implトレイトをregionckにどのように統合する必要がありますか? ディスカッション: 1 、 2
  • []一部のパラメーターが暗黙的で、一部が明示的である場合、タイプの指定を許可する必要がありますか? 例: fn foo<T>(x: impl Iterator<Item = T>>) ?
  • [] [ネストされたimplトレイトの使用に関するいくつかの懸念]https://github.com/rust-lang/rust/issues/34511#issuecomment-350715858)
  • [x] implの構文はexistential type Foo: Barまたはtype Foo = impl Barますか? (議論についてはここを参照してください)
  • [] implぎexistential type 「使用の定義」のセットは、implの単なるアイテムにするべきですか、それともimpl関数内にネストされたアイテムを含めるべきですか? (たとえば、ここを参照してください)
B-RFC-implemented B-unstable C-tracking-issue T-lang disposition-merge finished-final-comment-period

最も参考になるコメント

これはFCPが終了する前の最後のチャンスなので、自動自動特性に対して最後の議論をしたいと思います。 これは少しぎりぎりのことだと思いますので、現在の実装にコミットする前に、せいぜいこの問題に正式に対処したいと思います。

impl Traitフォローしていない人のために明確にするために、これは私が提示している問題です。 impl X型で表される型は、現在、その背後にある具体的な型が自動特性を実装している場合にのみ、自動特性を自動的に実装します。 具体的には、次のコード変更が行われた場合、関数はコンパイルを続行しますが、返される型がSendを実装しているという事実に依存する関数の使用は失敗します。

 fn does_some_operation() -> impl Future<Item=(), Error=()> {
-    let data_stored = Arc::new("hello");
+    let data_stored = Rc::new("hello");

     return some_long_operation.and_then(|other_stuff| {
         do_other_calculation_with(data_stored)
     });
}

(より簡単な例:動作中、内部変更により障害が発生します)

この問題は明確ではありません。 自動特性を「リーク」させるという非常に慎重な決定がありました。そうでない場合は、送信または同期以外の何かを返すすべての関数に+ !Send + !Syncを設定する必要があります。関数が返す具体的なタイプに単純に実装できない可能性のある他のカスタム自動特性についての不明確な話があります。 これらは、後で触れる2つの問題です。

まず、この問題に対する私の反対意見を簡単に述べたいと思います。これにより、関数本体を変更して、公開されているAPIを変更できます。 これにより、コードの保守性が直接低下します。

さびの発生を通して、使いやすさよりも冗長性の側で誤りを犯す決定がなされてきました。 新規参入者がこれらを見ると、冗長性のために冗長性であると考えますが、そうではありません。 構造体にコピーを自動的に実装させないか、関数シグネチャですべての型を明示的にするかどうかにかかわらず、各決定は保守性のためです。

私が人々にRustを紹介するとき、確かに、私は彼らにスピード、生産​​性、メモリの安全性を示すことができます。 しかし、行くにはスピードがあります。 エイダはメモリ安全性があります。 Pythonには生産性があります。 Rustが持っているものはこれらすべてに勝っており、保守性があります。 ライブラリの作成者がアルゴリズムをより効率的に変更したい場合、またはクレートの構造をやり直したい場合は、間違いを犯したときに通知するという強力な保証がコンパイラからあります。 さびたところで、私のコードはメモリの安全性だけでなく、ロジックとインターフェイスの観点からも機能し続けると確信できます。 _Rustのすべての関数インターフェースは、関数の型宣言で完全に表現できます_。

impl Traitそのまま安定させることは、この信念に反する可能性が高いです。 確かに、コードをすばやく作成するためには非常に便利ですが、プロトタイプを作成する場合はPythonを使用します。 Rustは、短期間の書き込み専用コードではなく、長期的な保守性が必要な場合に選択される言語です。


ここでも、問題が明確ではないため、これが悪い可能性は「大きい」と言えます。 そもそも「自動特性」の全体的な考え方は非明示的です。 SendとSyncは、パブリック宣言ではなく、構造体の内容に基づいて実装されます。 この決定はさびのためにうまくいったので、同様に行動するimpl Traitもうまくいく可能性があります。

ただし、関数と構造はコードベースで異なる方法で使用され、これらは同じ問題ではありません。

構造体のフィールドを変更すると、プライベートフィールドであっても、実際の内容を変更していることがすぐにわかります。 非送信または非同期フィールドを持つ構造はその選択を行い、ライブラリのメンテナは、PRが構造のフィールドを変更するときに再確認することを知っています。

関数の内部を変更する場合、パフォーマンスと正確さの両方に影響を与える可能性があることは明らかです。 ただし、Rustでは、正しいタイプが返されていることを確認する必要はありません。 関数宣言は私たちが守らなければならない厳しい契約であり、 rustcは私たちの後ろを監視します。 これは、構造体の自動特性と関数の戻り値の間の細い線ですが、関数の内部を変更することははるかに日常的です。 ジェネレーターを利用した完全なFutureができたら、 -> impl Future返す関数を変更するのがさらに日常的になります。 これらはすべて、コンパイラがそれをキャッチしない場合、変更された送信/同期実装について作成者がスクリーニングする必要がある変更になります。

これを解決するために、元のRFCの説明とと判断できます。 保守的なimpl特性RFCのこのセクションでは、自動特性のリークに関する最大の議論を示しています(「OIBIT」は自動特性の古い名前です)。

これに対する私の主な回答はすでに説明しましたが、最後にもう1つ注意しておきます。 構造のレイアウトを変更することはそれほど一般的ではありません。 それを防ぐことができます。 関数が同じ自動特性を実装し続けることを保証するための保守の負担は、関数が大幅に変更されるという理由だけで、構造のそれよりも大きくなります。


最後に、自動自動特性だけが選択肢ではないことをお伝えしたいと思います。 これは私たちが選択したオプションですが、オプトアウトの自動特性の代替手段は依然として代替手段です。

非送信/非同期アイテムを返す関数に、状態+ !Send + !Syncか、それらの境界を持つ特性(別名?)を返すように要求することができます。 これは良い決定ではありませんが、現在選択しているものよりも良いかもしれません。

カスタム自動特性に関する懸念については、新しい自動特性は実装されるべきではないと主張します。自動特性の後に導入された新しいタイプに対してのみ実装されます。 これは、私が現在対処できるよりも多くの問題を提供する可能性がありますが、より多くの設計で対処できない問題ではありません。


これは非常に遅く、非常に長い間巻き込まれており、私は以前にこれらの異議を唱えたことがあると確信しています。 最後にもう一度コメントできてうれしいです。そして、私たちが下している決定に完全に満足していることを確認します。

読んでいただきありがとうございます。最終決定により、Rustが最善の方向に進むことを願っています。

全てのコメント417件

@aturon実際にRFCをリポジトリに入れることはできますか? ( @mbrubeckは、これが問題でコメントしました。)

終わり。

実装の最初の試みは#35091です(昨年から私のブランチを数えると2番目です)。

私が遭遇した問題の1つは、寿命に関するものです。 型推論は、領域変数を_どこにでも_配置するのが好きで、領域チェックの変更がなければ、これらの変数はローカルスコープ以外のものを推論しません。
ただし、具象型はエクスポート可能である必要があるため、 'staticに制限し、早期バインドの有効期間パラメーターに明示的に名前を付けましたが、関数が含まれている場合は、それらのいずれにもなりません。文字列リテラルでさえ、 'static 、それはほとんど完全に役に立たない。

リージョンチェック自体に影響を与えないと私が考えた1つのことは、ライフタイムを消去することです。

  • impl Traitの具体的なタイプを公開するものは、存続期間を気にする必要はありません- Reveal::Allすばやく検索すると、コンパイラーではすでにそうであることがわかります
  • 関数の戻り型のimpl Traitすべての具象型に境界を設定する必要があります。これは、その関数の呼び出しよりも長生きすることを意味します。つまり、任意の存続期間は、必然的に'staticいずれかになります。 'aと'b最短」)
  • impl Traitの暗黙のライフタイムパラメータの分散を選択する必要があります(つまり、タイプパラメータの場合と同様に、スコープ内のすべてのライフタイムパラメータで):不変性は最も簡単で、呼び出し先により多くの制御を与えますが、反変性は呼び出し元にさらに、リターンタイプのすべてのライフタイムが反変の位置にあることを確認する必要があります(不変ではなく共変タイプのパラメーターと同じ)
  • 自動特性リークメカニズムでは、別の関数で特性境界をコンクリートタイプに設定する必要があります-ライフタイムを消去し、どのライフタイムがどこに行くのかわからないため、コンクリートタイプのすべての消去されたライフタイムを置き換える必要がありますすべての実際の寿命パラメータの中で最短の寿命よりも短くないことが保証されている新しい推論変数を使用します。 問題は、トレイトimplsがより強力なライフタイム関係(たとえば、 X<'a, 'a>またはX<'static> )を必要とする可能性があるという事実にあります。生涯

自動特性リークに関する最後のポイントは私の唯一の心配です。他のすべては簡単に思えます。
現時点では、リージョンチェックのどれだけをそのまま再利用できるかは完全には明らかではありません。 うまくいけば、すべて。

cc @ rust-lang / lang

@eddyb

しかし、生涯はimpl Trait重要です-例:

fn get_debug_str(s: &str) -> impl fmt::Debug {
    s
}

fn get_debug_string(s: &str) -> impl fmt::Debug {
    s.to_string()
}

fn good(s: &str) -> Box<fmt::Debug+'static> {
    // if this does not compile, that would be quite annoying
    Box::new(get_debug_string())
}

fn bad(s: &str) -> Box<fmt::Debug+'static> {
    // if this *does* compile, we have a problem
    Box::new(get_debug_str())
}

私はRFCスレッドで何度か言及しました

トレイトオブジェクトなしのバージョン:

fn as_debug(s: &str) -> impl fmt::Debug;

fn example() {
    let mut s = String::new("hello");
    let debug = as_debug(&s);
    s.truncate(0);
    println!("{:?}", debug);
}

これは、 as_debug定義に応じて、UBであるかどうかのどちらかです。

@ arielb1ああ、そうです、私がやった理由の1つは、実際には機能しないことを除いて、匿名のレイトバウンドパラメータではなく、ライフタイムパラメータのみをキャプチャすることであったことを忘れました。

@ arielb1具体的なタイプの事前消去で見つかったライフタイムと、署名のレイトバウンドのライフタイムの間に、厳密な'a outlives 'bインストールしないことは悪い考えではないかもしれません。ここで、 'aは'staticまたは生涯パラメータ以外の_何か_です。 'bは、 impl Traitの具体的なタイプで表示されます。

ここに返信するのに時間がかかってすみません。 だから私は考えてきました
この問題について。 私の気持ちは、最終的にはそうしなければならないということです(そして
したい)新しい種類の制約でregionckを拡張する-私はそれを呼びます
\in制約。これは、 '0 \in {'a, 'b, 'c}ようなものを言うことができるためです。つまり、 '0使用される領域は次のようになります。
'a 、 'b 、または'c 。 統合するための最良の方法がわかりません
これはそれ自体を解決することになります-確かに\inセットがシングルトンの場合
セット、それは単なる同等の関係です(これは現在、
一流のものですが、2つの境界から構成することができます)、しかし
そうでなければ、それは物事を複雑にします。

これはすべて、一連の領域制約を作成したいという私の願望に関連しています
今日の私たちよりも表現力豊かです。 確かに人は作曲することができます
\in ORおよび==制約のうち
表現上の制約は解決が難しく、 \inはありません。

とにかく、ここで私の考えを少し説明させてください。 これで作業しましょう
例:

pub fn foo<'a,'b>(x: &'a [u32], y: &'b [u32]) -> impl Iterator<Item=u32> {...}

impl Traitの最も正確な脱糖は、おそらく
新しいタイプ:

pub struct FooReturn<'a, 'b> {
    field: XXX // for some suitable type XXX
}

impl<'a,'b> Iterator for FooReturn<'a,'b> {
    type Item = <XXX as Iterator>::Item;
}

これで、 impl Iterator<Item=u32>のfooは次のように動作するはずです。
FooReturn<'a,'b>は動作します。 ただし、完全に一致するわけではありません。 一
たとえば、eddybが育てたように、違いは分散です-私は
impl Fooような型を型に対して不変にすることを想定しています
fooパラメータ。 ただし、自動特性の動作は機能します。
(一致が理想的ではない可能性がある別の領域は、
impl Iterator抽象化を「ピアス」する機能、つまりコード
抽象化の「内部」は正確なタイプを知っています-それからそれはソートします
暗黙の「アンラップ」操作が実行されます。)

いくつかの点で、より適切な一致は、一種の合成特性を検討することです。

trait FooReturn<'a,'b> {
    type Type: Iterator<Item=u32>;
}

impl<'a,'b> FooReturn<'a,'b> for () {
    type Type = XXX;
}

これで、 impl Iteratorタイプを<() as FooReturn<'a,'b>>::Typeように考えることができます。 これも完全に一致するわけではありません。
通常はそれを正規化します。 あなたは専門化を使用することを想像するかもしれません
それを防ぐために:

trait FooReturn<'a,'b> {
    type Type: Iterator<Item=u32>;
}

impl<'a,'b> FooReturn<'a,'b> for () {
    default type Type = XXX; // can't really be specialized, but wev
}

この場合、 <() as FooReturn<'a,'b>>::Typeは正規化されませんが、
そして、私たちははるかに近い一致を持っています。 特に、分散は動作します
正しい; 「内部」にあるタイプが必要な場合
抽象化、それらは同じですが、許可されています
ノーマライズ。 ただし、落とし穴があります。自動トレイトのものはそうではありません。
かなりうまくいきます。 (ここで物事を調和させることを検討したいかもしれませんが、
実際に。)

とにかく、これらの潜在的な脱糖を探求する私のポイントは
_実際の_脱糖として「implTrait」を実装することをお勧めします
(それはいいかもしれませんが...)しかし、私たちの仕事に直感を与えるためです。 私
2番目の脱糖(予測の観点から)は
私たちを前進させるのに非常に役立つものです。

この投影脱糖が本当に役立つガイドの1つは
「長生きする」関係。 <() as FooReturn<'a,'b>>::Type: 'xかどうかを確認したい場合、RFC 1214は、これを証明できることを示しています。
'a: 'x _および_ 'b: 'xが成立する限り。 これは私たちが望む方法だと思います
implトレイトの処理も行います。

トランス時、および自動特性の場合、 XXXを知る必要があります
もちろんです。 ここでの基本的な考え方は、タイプを作成することだと思います
XXX変数であり、返される実際の値を確認します
すべてXXXで統合できます。 その型変数は、理論的には、
私たちの答えを教えてください。 しかしもちろん問題はこのタイプです
変数は、スコープ内にない多くの領域を参照する場合があります
fn署名-たとえば、fn本体の領域。 (これと同じ問題
タイプでは発生しません。 技術的には、
たとえば、fn本体の構造体宣言であり、名前を付けることはできません。
これは一種の人為的な制限です-移動することもできます
fnの外側の構造体。)

構造体の脱糖またはimplの両方を見ると、
(Rustの字句構造で暗黙的に) XXXができる制限
'staticまたは'aや'bようなライフタイムのいずれかにのみ名前を付けます。
関数シグネチャに表示されます。 それは私たちがそうではないことです
ここでモデリング。 私はそれを行うための最良の方法がわかりません-いくつかのタイプ
推論スキームには、スコープのより直接的な表現があり、
クロージャを手伝うために、私はいつもそれをRustに追加したいと思っていました。 しかし
まず、小さなデルタについて考えてみましょう。

これが\in制約の由来です。 追加することを想像することができます
(基本的に) FR(XXX) \subset {'a, 'b} -というタイプチェックルール
つまり、XXXに表示される「無料の地域」は'aと
'b 。 これは、最終的に\in要件に変換されます。
XXX表示されるさまざまな地域。

実際の例を見てみましょう。

fn foo<'a,'b>(x: &'a [u32], y: &'b [u32]) -> impl Iterator<Item=u32> {
    if condition { x.iter().cloned() } else { y.iter().cloned() }
}

ここで、 conditionがtrueの場合のタイプは、次のようになります。
Cloned<SliceIter<'a, i32>> 。 しかし、 conditionが偽の場合、
Cloned<SliceIter<'b, i32>>欲しい。 もちろん、どちらの場合も
(タイプ/リージョン変数に数値を使用して)次のようになります。

Cloned<SliceIter<'0, i32>> <: 0
'a: '0 // because the source is x.iter()
Cloned<SliceIter<'1, i32>> <: 0
'b: '1 // because the source is y.iter()

次に、変数0をCloned<SliceIter<'2, i32>>にインスタンス化すると、
'0: '2と'1: '2 、または地域関係の合計セットがあります
お気に入り:

'a: '0
'0: '2
'b: '1
'1: '2
'2: 'body // the lifetime of the fn body

では、 '2はどのような値を使用する必要がありますか?追加もあります
'2 in {'a, 'b}という制約。書かれたfnで、私は私たちが思う
'aも'bもエラーを報告する必要があります。
正しい選択。ただし、興味深いことに、制約'a: 'bを追加すると、正しい値( 'b )が存在します。

_normal_アルゴリズムを実行するだけでは、次のようになることに注意してください。
'2は'bodyです。 \in関係を処理する方法がわかりません
徹底的な検索を除いて(私はいくつかの特別なものを想像することができますが
ケース)。

OK、それは私が得た限りです。 =)

PR#35091で、 @ arielb1は次のように書いています。

私は「暗黙の特性ですべての生涯をキャプチャする」アプローチが好きではなく、生涯のエリジオンのようなものを好みます。

ここで議論する方が理にかなっていると思いました。 @ arielb1 、あなたが考えていることについてもっと詳しく説明してもらえますか? 私は上記の製の類似の面では、私はあなたが基本的にnewtypeの上または投影のパラメータのいずれかとして現れる寿命のセット(すなわち、「剪定」について話していると思います<() as FooReturn<'a>>::Type代わりに<() as FooReturn<'a,'b>>::Typeか何か?

存在する生涯エリジオンルールがこの点で良いガイドになるとは思いません。 &selfの生涯のみを含めるように選択した場合、必ずしも含めることができるとは限りません。 Self構造体からの型パラメーター、またはメソッドからの型パラメーター。これらには、他のライフタイムのいくつかに名前を付ける必要があるWF条件がある場合があるためです。

とにかく、あなたが考えているルールと、おそらくその利点を説明するいくつかの例を見るのは素晴らしいことです。 :)(また、選択をオーバーライドするには、いくつかの構文が必要になると思います。)他のすべての条件が同じであれば、N個のライフタイムから選択する必要がない場合は、それをお勧めします。

impl Traitとプライバシーの相互作用がどこでも議論されているのを見たことがありません。
これで、 fn f() -> impl Traitは、トレイトオブジェクトfn f() -> Box<Trait>と同様に、プライベートタイプS: Trait fn f() -> impl Traitを返すことができます。 つまり、プライベートタイプのオブジェクトは、匿名化された形式でモジュールの外を自由に歩くことができます。
これは合理的で望ましいようです。タイプ自体は実装の詳細であり、パブリック特性Trait介して利用できるインターフェースのみがパブリックです。
ただし、特性オブジェクトとimpl Trait 1つの違いがあります。 特性オブジェクトのみを使用すると、プライベートタイプのすべての特性メソッドが内部リンケージを取得できますが、関数ポインターを介して引き続き呼び出すことができます。 impl Trait使用すると、プライベートタイプのトレイトメソッドを他の変換ユニットから直接呼び出すことができます。 シンボルの「内部化」を行うアルゴリズムは、 impl Traitで匿名化されていないタイプ、または非常に悲観的であるタイプに対してのみ、メソッドを内部化するためにさらに努力する必要があります。

@nikomatsakis

fooを書く「明示的な」方法は次のようになります

fn foo<'a: 'c,'b: 'c,'c>(x: &'a [u32], y: &'b [u32]) -> impl Iterator<Item=u32> + 'c {
    if condition { x.iter().cloned() } else { y.iter().cloned() }
}

ここでは、寿命の限界について疑問の余地はありません。 明らかに、毎回バウンドライフタイムを記述しなければならないことは非常に繰り返しになります。 しかし、私たちがその種の繰り返しに対処する方法は、一般的に生涯のエリジオンによるものです。 foo場合、省略は失敗し、プログラマーにライフタイムを明示的に指定するように強制します。

@eddybがimpl Traitの特定の場合にのみ実行し、それ以外の場合には実行しなかったため、明示性に敏感なライフタイム

@ arielb1うーん、私が議論した「脱糖」の観点から、この提案された構文をどのように考えるかは100%わかりません。 ライフタイムバウンドのように見えるものを指定できますが、私たちが推測しようとしているのは、ほとんどの場合、非表示タイプに表示されるライフタイムです。 これは、多くても1つのライフタイムを「非表示」にすることができることを示唆していますか(正確に指定する必要がありますか?)

「単一のライフタイムパラメータ」で十分であるとは限らないようです。

fn foo<'a, 'b>(x: &'a [u32], y: &'b [u32]) -> impl Iterator<Item=u32> {
    x.iter().chain(y).cloned()
}

この場合、非表示のイテレータタイプは、 'aと'b両方を参照します(ただし、どちらもバリアントですが、不変の例を考え出すことができると思います)。

それで、 @ aturonと私はこの問題についていくらか話し合い、共有したいと思いました。 ここには実際にいくつかの直交する質問があり、それらを分離したいと思います。 最初の質問は、「非表示のタイプで使用できる可能性のあるタイプ/ライフタイムパラメーターは何ですか?」です。 default typeへの(準)脱糖化に関しては、これは「導入する特性にどのタイプのパラメーターが表示されるか」に帰着します。 したがって、たとえば、この関数の場合:

fn foo<'a, 'b, T>() -> impl Trait { ... }

次のようなものに脱糖されます:

fn foo<'a, 'b, T>() -> <() as Foo<...>>::Type { ... }
trait Foo<...> {
  type Type: Trait;
}
impl<...> Foo<...> for () {
  default type Type = /* inferred */;
}

次に、この質問は「トレイトFooとそのimplにどのタイプのパラメーターが表示されるか」に帰着しますか? 基本的に、ここでは...です。 明らかにこれには、 Trait自体によって使用されるように見える型パラメーターのセットが含まれますが、追加の型パラメーターは何ですか? (前に述べたように、この脱糖は、自動特性のリークを除いて100%忠実であり、特殊な実装に対しても自動特性をリークする必要があると主張します。)

私たちが使用しているデフォルトの答えは「それらすべて」であるため、ここでは...は'a, 'b, T (表示される可能性のある匿名パラメーターとともに)。 これは妥当なデフォルトである可能性がありますが、必ずしも最良のデフォルトではありません。 ( @ arielb1が指摘したように。)

<() as Foo<...>>::Type ( impl Trait特定の不透明なインスタンス化を'xよりも長生きすることを確認するには、効果的に表示する必要があるため、これは長生きの関係に影響を与えます。その...: 'x (つまり、すべてのライフタイムとタイプパラメーター)。

これが、ライフタイムパラメータを考慮するだけでは不十分だと私が言う理由です。 foo::<'a0, 'b0, &'c0 i32>ようなfooへの呼び出しがあると想像してください。 これは、3つのライフタイムすべて'[abc]0 'xよりも長生きする必要があることを意味します。つまり、戻り値が使用されている限り、これは関数に与えられたすべてのデータの貸し出しをプロローグします。 。 しかし、 @ arielb1が指摘したように、エリジオンは、これが通常必要以上に長くなることを示唆しています。

ですから、私たちに必要なのは次のことだと思います。

  • おそらくエリジオンからの意図を使用して、合理的なデフォルトに落ち着く。
  • デフォルトが適切でない場合の明示的な構文を使用する。

@aturonは、明示的な構文としてimpl<...> Traitようなものを吐き出しましたが、これは妥当なようです。 したがって、次のように書くことができます。

fn foo<'a, 'b, T>(...) -> impl<T> Trait { }

非表示の型が実際には'aまたは'b Tおらず、 impl<'a> Traitと記述して、 'bもTもキャプチャされないことを示す場合があります。

デフォルトに関しては、より多くのデータがあると非常に便利なようですが、一般的なエリジオンのロジックでは、該当する場合は、 selfのタイプで指定されたすべてのパラメーターをキャプチャするのが適切であることが示されています。 たとえば、 fn foo<'a,'b>(&'a self, v: &'b [u8])あり、タイプがBar<'c, X>場合、 selfのタイプは&'a Bar<'c, X>になるため、 'aをキャプチャします。デフォルトでは'c 、およびXですが、 'bはありません。


もう1つの関連する注意事項は、ライフタイムバウンドの意味です。 サウンドの有効期間の境界には、変更してはならない既存の意味があると思います。 impl (Trait+'a)と書くと、非表示のタイプT 'a長生きすることを意味します。 同様に、 impl (Trait+'static)と記述して、借用したポインターが存在しないことを示すことができます(一部のライフタイムがキャプチャされた場合でも)。 非表示型T推論する場合、これは$T: 'staticようなライフタイム境界を意味します。ここで、 $Tは非表示型用に作成する推論変数です。 これは通常の方法で処理されます。 非表示の型が非表示になっている発信者の観点からすると、 'static境界により、ライフタイムパラメータがキャプチャされている場合でも、 impl (Trait+'static) 'staticも長生きすると結論付けることができます。

ここでは、脱糖が動作するのとまったく同じように動作します。

fn foo<'a, 'b, T>() -> <() as Foo<'a, 'b, 'T>>::Type { ... }
trait Foo<'a, 'b, T> {
  type Type: Trait + 'static; // <-- note the `'static` bound appears here
}
impl<'a, 'b, T> Foo<...> for () {
  default type Type = /* something that doesn't reference `'a`, `'b`, or `T` */;
}

これはすべて、推論から直交しています。 「choosefrom」制約の概念を追加し、いくつかのヒューリスティックと、場合によっては徹底的な検索を使用して推論を変更する必要があります(RFC 1214の経験から、保守的なフォールバックを使用したヒューリスティックは実際に非常に遠くまで到達できることがわかります。おそらくどこかに問題があるかもしれませんが、私はこの点で制限にぶつかっている人々を知りません)。 確かに、 'staticや 'a`のようなライフタイム境界を追加すると、推論に影響を与える可能性があるため、役立ちますが、それは完全な解決策ではありません。たとえば、呼び出し元に表示され、APIの一部になります。これは望ましくない場合があります。

可能なオプション:

出力パラメータの省略による明示的な有効期間の制限

今日の特性オブジェクトと同様に、 impl Traitオブジェクトには、エリジオンルールを使用して推測される単一のライフタイムバインドパラメータがあります。

短所:非人間工学的
利点:明確

「すべての一般的な」エリジオンによる明示的な存続期間の境界

今日の特性オブジェクトと同様に、 impl Traitオブジェクトには単一のライフタイムバインドパラメータがあります。

ただし、エリジオンは、すべての明示的なパラメーターよりも長持ちする新しい早期バインドパラメーターを作成します。

fn foo<T>(&T) -> impl Foo
-->
fn foo<'total, T: 'total>(&T) -> impl Foo + 'total

短所:早期バインドパラメータを追加します

もっと。

impl Trait + 'aと借用でこの問題に遭遇しました: https :

この変更を正しく理解している場合(そしてその可能性はおそらく低いです!)、この遊び場コードは機能するはずです:

https://play.rust-lang.org/?gist=496ec05e6fa9d3a761df09c95297aa2a&version=nightly&backtrace=0

ThingOneとThingTwoどちらも、 Thing特性を実装しています。 buildは、 Thingを実装するものを返すと言っています。 しかし、それはコンパイルされません。 だから私は明らかに何かを誤解しています。

その「何か」には型が必要ですが、あなたの場合、2つの競合する型があります。 @nikomatsakisは以前、型の不一致が表示されたときにThingOne | ThingTwoを作成して、これを一般的に機能させることを提案しました。

@eddyb ThingOne | ThingTwoについて詳しく教えていただけますか? 実行時にタイプしかわからない場合は、 Boxが必要ではありませんか? それとも一種のenumですか?

ええ、それはアドホックなenumようなタイプであり、可能であれば、トレイトメソッドの呼び出しをそのバリアントに委任します。

そんなことも以前から欲しかった。 匿名列挙型RFC: https :

不一致でこれらのタイプを作成するだけの場合、バリアントが異なるため(一般化された形式の問題です)、推論駆動型の場合にうまく機能することはまれです。
また、パターンマッチングがないことから何かを得ることができます(明らかに互いに素な場合を除いて?)。
ただし、IMO委任シュガーは、 T | Tを取得できたとしても、関連するすべてのケースで「正常に機能」します。

それらの文の他の暗黙の半分を詳しく説明していただけますか? 私はそれのほとんどを理解していません、そして私がいくつかの文脈を逃しているのではないかと思います。 共用体型の問題に暗黙的に対応していましたか? そのRFCは単純に匿名の列挙型であり、共用体型ではありません- (T|T)はResult<T, T>とまったく同じくらい問題があります。

ああ、気にしないでください、私は提案を混乱させました(私が失敗したHDDを整理するまでモバイルでも立ち往生しているので、Twitterのように聞こえることをお詫びします)。

私は(位置、つまりT|U != U|T )匿名の列挙型に興味をそそられます。可変個引数のジェネリックがあれば( hlistを使用してこれを回避できます)、ライブラリで実験できると思います。 constジェネリック(同上、ペアノ番号付き)。

しかし同時に、何かに対する言語サポートがあれば、それは匿名の列挙型ではなく、共用体型になります。 たとえば、 Resultなく、エラータイプ(名前付きラッパーの面倒な作業を回避するため)。

ここが適切な場所かどうかはわかりませんが、なぜimplようなキーワードが必要なのですか? ディスカッションが見つかりませんでした(私のせいかもしれません)。

関数がimplTraitを返す場合、その本体はTraitを実装する任意のタイプの値を返すことができます

以来

fn bar(a: &Foo) {
  ...
}

「特性Fooを実装する型への参照を受け入れる」という意味です。

fn bar() -> Foo {
  ...
}

「特性Fooを実装する型を返す」という意味です。 これは不可能ですか?

@ kud1ingの理由は、動的サイズの戻り値のサポートが将来追加された場合に、動的サイズの型Traitを返す関数を持つ可能性を排除しないためです。 現在、 Traitはすでに有効なDSTですが、DSTを返すことはできないため、サイズを変更するためにボックス化する必要があります。

編集:リンクされたRFCスレッドでこれについていくつかの議論があります。

ええと、動的にサイズ設定された戻り値が追加されるかどうかに関係なく、私は現在の構文を好みます。 特性オブジェクトで発生することとは異なり、これは型消去ではなく、「パラメータf: &FooはFooを意味するものを取りますが、これはFooを意味するものを返します」誤解を招く可能性があります。

私はRFCの議論から、現在implはプレースホルダーの実装であり、 implはあまり望まれていないことを収集しました。 戻り値がDSTでない場合、 implトレイトが必要ない理由はありますか?

「自動トレイトリーク」を処理するための現在のimpl手法には問題があると思います。 代わりに、DAG順序を適用して、fn fn foo() -> impl Iteratorを定義し、呼び出し元がfn bar() { ... foo() ... }場合、 bar()前にfoo()を入力チェックする必要があります。 bar() (非表示タイプが何であるかを知るため)。 サイクルが発生した場合は、エラーを報告します。 これは保守的なスタンスであり、おそらくもっとうまくいく可能性がありますが、自動トレイトの義務を収集して最後にチェックするという現在の手法は、一般的には機能しないと思います。 たとえば、スペシャライゼーションではうまく機能しません。

(厳密なDAGを要求するよりも寛容である可能性がある別の可能性は、両方のfnsをある程度「一緒に」タイプチェックすることです。これは、特性システムを少し再構築した後でのみ検討する必要があると思います。)

@Nercuryわかりません。 fn foo() -> Traitが-> impl Traitを意味することを望まない理由があるかどうか尋ねていますか?

@nikomatsakisはい、私は正確にそれを求めていました、混乱した言葉で申し訳ありません:)。 implキーワードなしでこれを行う方が簡単だと思いました。これは、この動作がまさに期待どおりであるためです(trait returntypeの代わりにconcretetypeが返される場合)。 しかし、何かが足りないかもしれないので、私は尋ねていました。

違いは、 impl Traitを返す関数は常に同じ型を返すことです。これは、基本的に型推論を返します。 IIUC、 Traitだけを返す関数は、その特性の実装を動的に返すことができますが、呼び出し元は、 box foo()ようなものを介して戻り値にスペースを割り当てる準備をする必要があります。

@Nercury単純な理由は、 -> Trait構文にはすでに意味があるため、この機能には別のものを使用する必要があるということです。

私は実際に人々がデフォルトで両方の種類の動作を期待しているのを見てきました、そしてこの種の混乱は十分頻繁に起こります私は正直に言ってfn foo() -> Traitは何も意味しない(またはデフォルトで警告である)と明示的でした「コンパイル時に選択できるが、呼び出し元には表示されないタイプ」の場合と「トレイトを実装する任意のタイプに動的にディスパッチできるトレイトオブジェクト」の場合の両方のキーワード(例: fn foo() -> impl Trait vs fn foo() -> dyn Trait 。 しかし、明らかにそれらの船は出航しました。

コンパイラが関数のすべての異なる戻り型を保持する列挙型を生成せず、各バリアントへの引数を通過する特性を実装し、代わりにそれを返すのはなぜですか?

これは、許可された唯一の戻りタイプであるルールをバイパスします。

@NeoLegendsこれを手動で行うことはかなり一般的であり、そのためのいくつかの砂糖は素晴らしいかもimpl Traitまたは特性オブジェクトを返すこととはまったく異なるセマンティクスの3番目のセットであるため、そうではありませんこの議論に本当に関連しています。

@Ixrecええ、これは手動で行われていることは知っていますが、コンパイラーが生成する戻り型としての匿名列挙型の実際のユースケースは、イテレーターの長いチェーンや将来のアダプターのように、

この異なるセマンティクスはどうですか? 匿名の列挙型(匿名の列挙型RFCによるのではなく、コンパイラーが生成する限り)は、さまざまなバリアントを抽象化するトレイトのような共通のAPIがある場合にのみ、戻り値として実際に意味があります。 APIのコンシューマーが直接見ることのない列挙型のコンパイラーによって1つのタイプの制限が削除されただけで、通常のimplトレイトのように見えて動作する機能を提案しています。 消費者は常に「implTrait」のみを表示する必要があります。

匿名の自動生成された列挙型はimpl Traitに隠れたコストを与え、見逃しやすいので、それは考慮すべきことです。

「自動列挙型パススルー」は、オブジェクトセーフな特性にのみ意味があると思います。 impl Trait自体にも同じことが当てはまりますか?

@rpjohnstこれがない限り、実際のメソッドバリアントはクレートメタデータにあり、呼び出しサイトで単

@glaebhoerl

「自動列挙型パススルー」は、オブジェクトセーフな特性にのみ意味があると思います。 implトレイト自体にも同じことが当てはまりますか?

これは興味深い点です! 私は、impl特性を「脱糖」する正しい方法について議論してタイプの派生によく似ていることを意味しているようです。もちろんF<T>タイプの特性の実装を自動的に生成する場合は、ここで非常に注意する必要があるようです。 T 。

@nikomatsakis

問題は、さびの言葉で言えば

trait Foo {
    type Output;
    fn get() -> Self::Output;
}

fn foo() -> impl Foo {
    // ...
    // what is the type of return_type::get?
}

tl; drは、一般化されたニュータイプの派生は、vtableを単にtransmuteすることによって実装された(そして実装されている)ということです-結局のところ、vtableはタイプの関数で構成され、タイプとそのニュータイプは同じ表現を持っています、それで大丈夫なはずですよね? しかし、それらの関数が、指定された型のID (表現ではなく)の型レベルの分岐によって決定される型も使用する場合、たとえば、型関数または関連する型(またはHaskell、GADT)を使用する場合は機能しません。 これらのタイプの表現にも互換性があるという保証はないためです。

この問題は、安全でないトランスミュートを使用している場合にのみ発生する可能性があることに注意してください。 代わりに、新しいタイプをどこでもラップ/アンラップし、すべてのメソッドを基本タイプからその実装にディスパッチするための退屈なボイラープレートコードを生成した場合(Rust IIRCの自動委任提案の一部のように?)、考えられる最悪の結果はタイプになりますエラーまたは多分ICE。 結局のところ、構造上、安全でないコードを使用しない場合、安全でない結果をもたらすことはできません。 同様に、ある種の「自動列挙パススルー」のコードを生成したが、そのためにunsafeプリミティブを使用しなかった場合、危険はありません。

(これが、 impl Traitで使用される特性、および/または自動列挙型パススルーが必然的にオブジェクトセーフである必要があるかどうかという私の最初の質問に関連するかどうか、またはどのように関連するかはわかりませんが?)

@rpjohnstコストをマークするために列挙型ケースをオプトインにすることができます:

fn foo() -> enum impl Trait { ... }

しかし、それはほぼ間違いなく別のRFCの糧です。

@glaebhoerlええ、私はこの問題を掘り下げるのに時間を費やし、少なくともここでは問題にならないだろうとかなり確信していました。

明らかなことであればお詫びしますが、 impl Traitが戻り型の特性メソッドに表示されない理由、またはそもそもそれがまったく意味があるかどうかを理解しようとしていますか? 例えば:

trait IterInto {
    type Output;
    fn iter_into(&self) -> impl Iterator<Item=impl Into<Self::Output>>;
}

@aldanorそれは完全に理にかなっており、AFAIKの意図はそれを機能させることですが、まだ実装されていません。

それはある意味理にかなっていますが、それは同じ基本的な機能ではありません(これは多くのところで議論されています):

// What that trait would desugar into:
trait IterInto {
    type Output;
    type X: Into<Self::Output>;
    type Y: Iterator<Item=Self::X>;
    fn iter_into(&self) -> Self::Y;
}

// What an implementation would desugar into:
impl InterInto for FooList {
    type Output = Foo;
    // These could potentially be left unspecified for
    // a similar effect, if we want to allow that.
    type X = impl Into<Foo>;
    type Y = impl Iterator<Item=Self::X>;
    fn iter_into(&self) -> Self::Y {...}
}

具体的には、 impl Trait for Type関連タイプのRHSのimpl Traitは、安定したRustに脱糖できないという点で、今日実装されている機能に似ていますが、 traitかもね。

これはおそらく手遅れであり、ほとんどがバイクシェッドであることを私は知っていますが、キーワードimplが導入された理由はどこかに文書化されていますか? 現在のRustコードには、「コンパイラーはここにどのタイプが入るかを理解する」、つまり_を言う方法がすでにあるように思えます。 ここでこれを再利用して構文を与えることはできませんか?

fn foo() -> _ as Iterator<Item=u8> {}

@jonhooこれは機能が行うことではなく、型は関数から返されるものではなく、選択されたAPI(およびOIBITは面倒なので)以外のすべてを隠す「セマンティックラッパー」です。

一部の関数がDAGを強制することで署名の型を推測できるようにすることもできますが、そのような機能は承認されておらず、「グローバル推論」に触れているため、Rustに追加される可能性はほとんどありません。

利用提案@Trait置き換えるための構文をimpl Trait述べたように、ここに。

Box<@MyTrait>や&@MyTraitような構成では、他のタイプの位置に拡張する方が簡単です。

@Trait場合はany T where T: Trait 、 ~Trait場合はsome T where T: Trait :

fn compose<T, U, V>(f: @Fn(T) -> U, g: @Fn(U) -> V) -> ~Fn(T) -> V {
    move |x| g(f(x))
}

fn func(t: T) -> Vでは、特性として、tまたはvを区別する必要はありません。

fn compose<T, U, V>(f: @Fn(T) -> U, g: @Fn(U) -> V) -> @Fn(T) -> V {
    move |x| g(f(x))
}

まだ動作します。

@ JF-Liu私は個人的にanyとsomeを1つのキーワード/印章にまとめることに反対していますが、単一の印章を持って元のimpl Traitように使用できることは技術的に正しいです

@ JF-Liu @ eddybシジルが言語から削除された理由がありました。 なぜその理由はこの場合には当てはまらないのでしょうか?

@は、言語から削除されるのではなく、パターンマッチングでも使用されます。

私が念頭に置いていたのは、AFAIKシジルが使いすぎていたということです。

構文bikesheding: impl Trait表記については非常に不満です。キーワード(エディターでは太字のフォント)を使用して型に名前を付けるのは大きすぎるためです。 CのstructとStroustroupの大音量の構文観察(スライド14)を覚えてい

https://internals.rust-lang.org/t/ideas-for-making-rust-easier-for-beginners/4761で、 @ konstinは<Trait>構文を提案しました。 特に入力位置では、とても見栄えがします。

fn take_iterator(iterator: <Iterator<Item=i32>>)

UFCSと多少競合するようですが、おそらくこれは解決できるでしょうか。

私も、少なくともリターンタイプの位置では、impl Traitの代わりに山かっこを使用する方が良い選択だと感じています。例:

fn returns_iter() -> <Iterator<Item=i32>> {...}
fn returns_closure() -> <FnOnce() -> bool> {...}

<Trait>構文がジェネリックスと競合している場合は、次のことを考慮してください。

Vec<<FnOnce() -> bool>> vs Vec<@FnOnce() -> bool>

Vec<FnOnce() -> bool>が許可されている場合は、 <Trait>をお勧めします。これは、ジェネリック型パラメーターと同等であることを意味します。 ただし、 Box<Trait>はBox<@Trait>とは異なるため、 <Trait>構文をあきらめる必要があります。

私はimplキーワード構文を好みます。なぜなら、ドキュメントをすばやく読むと、プロトタイプを誤読する方法が少なくなるからです。
どう思いますか ?

私は内部スレッドでこのRFCにスーパーセットを提案したことに気づいています(ここで私を指してくれてありがとう@matklad ):

次の例のように、関数パラメーターと戻り型の特性を山かっこで囲むことにより、それらを使用できるようにします。

fn transform(iter: <Iterator>) -> <Iterator> {
    // ...
}

次に、コンパイラーは、ジェネリックスに現在適用されているのと同じルールを使用してパラメーターをモノモーフィングします。 戻り型は、たとえば関数の実装から派生させることができます。 これは、このメソッドをBox<Trait_with_transform>呼び出したり、動的にディスパッチされたオブジェクトで一般的に使用したりすることはできないことを意味しますが、それでもルールはより寛容になります。 私はRFCの議論のすべてを読んだわけではないので、私が見逃したより良い解決策がすでにそこにあるかもしれません。

ドキュメントをすばやく読むと、プロトタイプを読み間違える可能性が少なくなるため、implキーワード構文を使用します。

構文の強調表示の別の色でうまくいくはずです。

Stroustrupによるこのペーパーでは、セクション7でC ++の概念に対する同様の構文上の選択について説明しています。http ://www.stroustrup.com/good_concepts.pdf

ジェネリックスとプレゼンスに同じ構文を使用しないでください。 それらは同じものではありません。 ジェネリックスを使用すると、呼び出し元は具体的なタイプを決定できますが、(この制限されたサブセットの)プレゼンスを使用すると、呼び出される関数を使用して具体的なタイプを決定できます。 この例:

fn transform(iter: <Iterator>) -> <Iterator>

これと同等である必要があります

fn transform<T: Iterator, U: Iterator>(iter: T) -> U

またはこれと同等である必要があります

fn transform(iter: impl Iterator) -> impl Iterator

最後の例は、夜間でも正しくコンパイルされず、イテレータトレイトでは実際には呼び出せませんが、 FromIterようなトレイトを使用すると、呼び出し元はインスタンスを作成して、それを関数に渡すことができます。彼らが通過しているものの具体的なタイプを決定するために。

構文は似ているはずですが、同じであってはなりません。

タイプ名の(ジェネリック)または(存在)の一部を区別する必要はありません。タイプが使用される場所によって異なります。 変数で使用される場合、引数と構造体フィールドは常にTのいずれかを受け入れ、fnで使用される場合、戻り型は常にTの一部を取得します。

  • Type 、 &Type 、 Box<Type>を具体的なデータ型、静的ディスパッチに使用します
  • @Trait 、 &@Trait 、 Box<@Trait>と、抽象データ型のジェネリック型パラメーター、静的ディスパッチを使用します
  • 抽象データ型、動的ディスパッチには&Trait 、 Box<Trait>を使用します

fn func(x: @Trait)はfn func<T: Trait>(x: T)と同等です。
fn func<T1: Trait, T2: Trait>(x: T1, y: T2)は、単純にfn func(x: <strong i="22">@Trait</strong>, y: @Trait)と書くことができます。
Tパラメータはfn func<T: Trait>(x: T, y: T)でも必要です。

struct Foo { field: <strong i="28">@Trait</strong> }はstruct Foo<T: Trait> { field: T }と同等です。

変数で使用される場合、引数と構造体フィールドは常にTのいずれかを受け入れ、fnで使用される場合、戻り型は常にTの一部を取得します。

現在、既存の汎用構文を使用して、安定したRustでany-of-Traitを返すことができます。 これは非常に頻繁に使用される機能です。 serde_json::de::from_sliceは、パラメータとして&[u8]を取り、 T where T: Deserializeを返します。

いくつかの特性を意味のある形で返すこともできます。これが、私たちが議論している機能です。 ジェネリックスを使用してボックス化されていないクロージャを返すことができないのと同じように、デシリアライズ関数に存在を使用することはできません。 それらは異なる機能です。

より馴染みのある例として、 Iterator::collectは任意のT where T: FromIterator<Self::Item>を返すことができます。これfn collect(self) -> any FromIterator<Self::Item>を意味します。

構文はどうですか
fn foo () -> _ : Trait { ... }
戻り値と
fn foo (m: _1, n: _2) -> _ : Trait where _1: Trait1, _2: Trait2 { ... }
パラメータについては?

私にとって、新しい提案はどれも、その優雅さの中でimpl Traitに近づくことはありません。 implは、すべてのRustプログラマーにすでに知られているキーワードであり、トレイトの実装に使用されるため、実際には、機能がそれ自体で何をしているのかを示唆しています。

ええ、既存のキーワードに固執することは私にとって理想的なようです。 プレゼンスにはimpl for 、ユニバーサルには

私は、 anyとsomeを1つのキーワード/印章にまとめることに個人的に反対しています。

@eddyb私はそれを

((∃ T . F⟨T⟩) → R)  →  ∀ T . (F⟨T⟩ → R)

編集:それは一方向であり、同型ではありません。


無関係: impl Traitを次のような他の共変位置でも許可する関連提案はありますか

〜さびfn foo(コールバック:F)-> Rここで、F:FnOnce(impl SomeTrait)-> R {callback(create_something())}〜

今のところ、これは必要な機能ではありません。 impl SomeTraitにいつでも具体的な時間を設定できるため、読みやすさが損なわれますが、それ以外は大したことではありません。

しかし、RFC 1522機能が安定した場合、 create_somethingがimpl SomeTrait (少なくともボックス化せずに)、上記のようなプログラムに型署名を割り当てることは

@Rufflewind現実の世界では、物事はそれほど明確ではなく、この機能は非常に特殊なブランドの存在です(Rustには現在いくつかあります)。

しかし、それでも、共分散を使用して、関数の引数の内側と外側でimpl Trait何を意味するかを判断するだけです。

それだけでは不十分です:

  • デフォルトの反対を使用
  • フィールドの型内の曖昧性解消( anyとsome両方が等しく望ましい場合)

@Rufflewindこれは、 impl Traitが何であるかについて間違ったブラケットのようです。 Haskellがこの関係を利用してforallキーワードのみを使用して普遍性と存在性の両方を表すことは知っていますが、これは私たちが議論している文脈ではうまくいきません。

この定義を例にとってみましょう。

fn foo(x: impl ArgTrait) -> impl ReturnTrait { ... }

「引数のimplは普遍的であり、戻り型のimplは実存的である」というルールを使用する場合、 foo関数項目型の型は論理的にこれです(メイクアップタイプ表記):

forall<T: ArgTrait>(exists<R: ReturnTrait>(fn(T) -> R))

implを技術的には普遍的または存在的のみを意味するものとして単純に扱い、ロジック自体を機能させることは実際には機能しません。 あなたはこれを得るでしょう:

forall<T: ArgTrait, R: ReturnTrait>(fn(T) -> R)

またはこれ:

exists<T: ArgTrait, R: ReturnTrait>(fn(T) -> R)

そして、これらのどちらも、論理的なルールによって私たちが望むものに還元されません。 したがって、最終的にany / someは、単一のキーワードでは捉えることができない重要な違いを捉えます。 std 、ユニバーサルをリターンポジションに配置したいという合理的な例もあります。 たとえば、このIteratorメソッドは次のとおりです。

fn collect<B>(self) -> B where B: FromIterator<Self::Item>;
// is equivalent to
fn collect(self) -> any FromIterator<Self::Item>;

そして、 implと引数/戻り規則でそれを書く方法はありません。

implを持つtl; drは、文脈上、普遍的または実存的のいずれかを示し、実際には2つの異なる意味を与えます。


参考までに、私の表記では、 @ Rufflewindが言及したforall / existsの関係は次のようになります。

fn(exists<T: Trait>(T)) -> R === forall<T: Trait>(fn(T) -> R)

これは、特性オブジェクト(存在)がジェネリック(ユニバーサル)と同等であるという概念に関連していますが、このimpl Trait質問には関連していません。

そうは言っても、私はもうany / some強く支持していません。 私は私たちが話していることについて正確に言いたかったのですが、 any / someはこの理論的および視覚的な素晴らしさを持っていますが、コンテキストでimplを使用しても問題ありませんルール。 私はそれがすべての一般的なケースをカバーし、文脈上のキーワード文法の問題を回避し、残りの名前付きタイプのパラメーターにドロップできると思います。

その点で、ユニバーサルの完全な一般性と一致させるには、最終的に名前付き実存の構文が必要になると思います。これにより、任意のwhere句と、署名の複数の場所で同じ実存を使用できるようになります。

要約すると、私は満足しているでしょう:

  • impl Traitは、ユニバーサルとプレゼンスの両方の省略形です(コンテキストに応じて)。
  • ユニバーサルとプレゼンスの両方の完全に一般的なロングハンドとしての名前付きタイプパラメータ。 (あまり一般的には必要ありません。)

implを技術的には普遍的または存在的のみを意味するものとして単純に扱い、ロジック自体を機能させることは実際には機能しません。 あなたはこれを得るでしょう:

@solson私にとって、「ナイーブな」変換は、数量化される型の記号をもたらすでしょう。 したがって、

〜さび(impl MyTrait)〜

のための単なる構文糖です

〜さび(存在するT)〜

これは単純なローカル変換です。 したがって、「 implは常に存在する」というルールに従う単純な翻訳は、次のようになります。

〜さびfn(存在するT)->(存在するR)〜

次に、関数の引数から数量詞を引き出すと、次のようになります。

〜さびにとってfn(T)->(存在するR)〜

したがって、 Tは常にそれ自体に対して存在しますが、関数型全体に対しては普遍的であるように見えます。


IMO、私はimplが実存型の事実上のキーワードになるかもしれないと思います。 将来的には、おそらく次のようなより複雑な存在型を構築する可能性があります。

~~さび(impl(Vec、T))〜 〜

ユニバーサルタイプと同様に(HRTB経由)

〜さび(<'a> FnOnce(&' a T)の場合)〜

@Rufflewind fn(T) -> (exists<R: ReturnTrait>(R))は論理的にexists<R: ReturnTrait>(fn(T) -> R)と同等ではないため、このビューは機能しません。これは、return-type impl Trait実際に意味するものです。

(少なくとも、実存主義のために選択された特定の証人が関連する型システムに通常適用される建設的論理ではありません。前者は、関数が、たとえば引数に基づいて返すさまざまな型を選択できることを意味し、後者は、 impl Traitの場合のように、関数のすべての呼び出しに対する1つの特定のタイプ。)

私たちも少し遠ざかっていると感じています。 コンテキストimplは妥協案としては問題ないと思います。この種の正当化に到達する必要はなく、特に役立つとは思いません(この種の論理的な接続に関しては、ルールを教えません。 )。

@solsonそうですね。存在を

(T → ∃R. f(R))  ⥇  ∃R. T → f(R)

これらは一般的に当てはまりますが:

(∃R. T → f(R))  →   T → ∃R. f(R)
(∀A. g(A) → T)  ↔  ((∃A. g(A)) → T)

最後のものは、ジェネリックとしての引数の存在の再解釈に責任があります。

編集:おっと、 (∀A. g(A) → T) → (∃A. g(A)) → Tは成り立ちます。

impl Traitを拡張して安定させるための詳細な提案を含むRFCを投稿しまし

https://github.com/rust-lang/rfcs/pull/1951が受け入れられたことは注目に値し

現在、これの状況はどうですか? 私たちは着陸したRFCを持っており、最初の実装を使用している人々がいますが、私は何をすべきかはっきりしていません。

#43869で、 -> impl Trait関数が純粋に発散する本体をサポートしていないことがわかりました。

fn do_it_later_but_cannot() -> impl Iterator<Item=u8> { //~ ERROR E0227
    unimplemented!()
}

これは予想されますか( !はIterator意味しないため)、またはバグと見なされますか?

戻り値として使用できるだけでなく、現在タイプを使用できるものとして(おそらく)推測される型を定義するのはどうですか?
何かのようなもの:
type Foo: FnOnce() -> f32 = #[infer];
またはキーワードで:
infer Foo: FnOnce() -> f32;

タイプFooは、リターンタイプ、パラメータータイプ、またはタイプを使用できるその他のタイプとして使用できますが、異なるタイプを必要とする2つの異なる場所で使用することは違法です。 type FnOnce() -> f32 、どちらの場合も

infer Foo: FnOnce() -> f32;

fn return_closure() -> Foo {
    || 0.1
}

fn return_closure2() -> Foo {
    || 0.2
}

fn main() {
    println!("{:?}, {:?}", return_closure()(), return_closure2()());
}

return_closureとreturn_closure2からの戻り型が両方ともFnOnce() -> f32であるため、これはコンパイルされるべきではありません。Rustでは2つのクロージャが同じ型を持たないため、それらの型は実際には異なります。 。 したがって、上記をコンパイルするには、2つの異なる推定型を定義する必要があります。

infer Foo: FnOnce() -> f32;
infer Foo2: FnOnce() -> f32; //Added this line

fn return_closure() -> Foo {
    || 0.1
}

fn return_closure2() -> Foo2 { //Changed Foo to Foo2
    || 0.2
}

fn main() {
    println!("{:?}, {:?}", return_closure()(), return_closure2()());
}

ここで何が起こっているのかは、inferキーワードが何をするのかを事前に知らなくても、コードを見れば非常に明白であり、非常に柔軟だと思います。

inferキーワード(またはマクロ)は、基本的に、使用されている場所に基づいて、型が何であるかを理解するようにコンパイラーに指示します。 コンパイラが型を推測できない場合、エラーがスローされます。これは、必要な型を絞り込むための十分な情報がない場合に発生する可能性があります(たとえば、推測された型がどこにも使用されていない場合、おそらく、その特定のケースを警告にする方が良いでしょう)、またはそれが使用されるすべての場所に適合するタイプを見つけることが不可能な場合(上記の例のように)。

@alvitawahttps : //github.com/rust-lang/rfcs/pull/2071を参照してください

@cramertjああ、そういうわけでこの問題はとても沈黙していました。

それで、 @ cramertjは、PRで遭遇したレイトバウンドリージョンの問題を解決するのが最善だと私がどのように考えているかについて私に尋ねていました。 私の考えでは、おそらく実装を少し「リツール」して、 anonymous type Fooモデルを楽しみにしています。

文脈上、アイデアは大まかにそれです

fn foo<'a, 'b, T, U>() -> impl Debug + 'a

このようなものに(一種の)脱糖されます

anonymous type Foo<'a, T, U>: Debug + 'a
fn foo<'a, 'b, T, U>() -> Foo<'a, T, U>

このフォームでは、 Foo引数として表示されるため、どの汎用パラメーターがキャプチャされるかを確認できます。特に、 'bは、の特性参照に表示されないため、キャプチャされません。とにかく、タイプパラメータTとU常にです。

とにかく、現在コンパイラーでは、 impl Debug参照がある場合、この匿名型を-事実上-表すdef-idを作成します。 次に、ジェネリックパラメーターを計算するgenerics_ofクエリがあります。 現在、これは「囲む」コンテキストと同じもの、つまり関数foo返します。 これが私たちが変えたいものです。

「反対側」、つまりfooの署名では、 impl FooをTyAnonとして表します。 これは基本的に正しいです- TyAnonは、上記の脱糖で見られるFooへの参照を表しています。 しかし、このタイプの「substs」を取得する方法は、「identity」関数を使用することです。これは明らかに間違っています。または、少なくとも一般化されていません。

したがって、特にここで発生している一種の「名前空間違反」があります。 アイテムの「ID」サブスタンスを生成すると、通常、そのアイテムをタイプチェックするときに使用する置換が得られます。つまり、スコープ内のすべてのジェネリックパラメーターを使用します。 ただし、この場合、関数foo()内に表示されるFooへの参照を作成するため、 foo()のジェネリックパラメーターをSubsts 、 Fooものではありません。 今のところそれらは同じであるため、これはたまたま機能しますが、実際には正しくありません。

私たちがすべきことは次のようなもの

まず、 Fooのジェネリック型パラメーター(つまり、匿名型自体)を計算するときに、ジェネリックの新しいセットの作成を開始します。 当然、タイプも含まれます。 しかし、生涯にわたって、私たちは特性の境界を越えて、その中に現れる各領域を特定します。 これは、cramertjが作成したが異なります。

私たちがやりたいのは、表示される領域のセットを蓄積して並べ替え、 foo()の観点からそれらの領域の値を追跡FreeRegionの概念がありましたが、これはほとんど機能していましたが、 FreeRegionを早期に使用するものではなく、後期に使用するものにのみ使用します。)

おそらく最も簡単で最良のオプションはRegion<'tcx>使用することですが、導入されたバインダーを「キャンセル」するために、debruijnインデックスの深さをシフトする必要があります。 しかし、これはおそらく最良の選択です。

したがって、基本的にvisit_lifetimeでコールバックを取得すると、それらを初期の深さで表されるRegion<'tcx>変換します(バインダーを通過するときに追跡する必要があります)。 それらをベクトルに蓄積し、重複を排除します。

完了したら、2つのことがあります。

  • まず、ベクトル内の各領域に対して、ジェネリック領域パラメーターを作成する必要があります。 それらはすべて匿名の名前などを持つことができますが、それはそれほど重要ではありません(ただし、def-idなどが必要な場合があります...? RegionParameterDefデータ構造を確認する必要があります...) 。
  • 次に、ベクトル内の領域は、「substs」に使用したいものでもあります。

OK、それが不可解な場合は申し訳ありません。 私はそれをより明確に言う方法を完全に理解することはできません。 よくわからないことですが、今のところ、リージョンの処理はかなり複雑だと感じているので、リファクタリングしてより均一にする方法があるのではないでしょうか。 @eddybがここでいくつかの考えを持っていることを$ 10に賭けます。 ;)

@nikomatsakisそれの多くは私が@cramertjに言ったことと似ていると思いますが、もっと肉付けされています!

私は実存的なimpl Traitについて考えていましたが、慎重に進める必要があると思う奇妙なケースに遭遇しました。 この関数について考えてみましょう。

trait Foo<T> { }
impl Foo<()> for () { }
fn foo() -> impl Foo<impl Debug> {
  ()
}

プレイ時に検証できる

具体的には、ここで返される型( () )をどのように推測するかは明らかです。 impl Debugパラメーターのタイプをどのように推測するかはあまり明確ではありません。 つまり、この戻り値は-> ?Tようなものと考えることができます。ここで?T: Foo<?U>です。 ?T = ()という事実だけに基づいて、 ?Tと?Uの値を推測する必要があります。

現在、これを行うには、implが1つしかないという事実を利用します。 ただし、これは壊れやすいプロパティです。 新しいimplが追加されると、コードはコンパイルされなくなります。これは、 ?U何であるかを一意に判別できないためです。

これは、Rustの多くのシナリオで発生する可能性があります。これは十分に懸念されていますが、直交していますが、 impl Trait場合には別のことがあります。 impl Trait場合、ユーザーが型注釈を追加して推論をガイドする方法がありません。 また、そのような方法の計画も実際にはありません。 唯一の解決策は、fnインターフェースをimpl Foo<()>またはその他の明示的なものに変更することです。

将来的には、 abstract typeを使用して、ユーザーが非表示の値を明示的に指定できるようにすることを想像できます(または、 _を使用して、不完全なヒントを使用することもできます)。同じパブリックインターフェイス

abstract type X: Debug = ();
fn foo() -> impl Foo<X> {
  ()
}

それでも、関連する型バインディングを除いて、既存のimplトレイトの「ネストされた」使用を安定させることを避けるのが賢明だと思います(たとえば、 impl Iterator<Item = impl Debug>はこれらのあいまいさの影響を受けません)。

impl Traitの場合、ユーザーが型注釈を追加して推論をガイドする方法がありません。 また、そのような方法の計画も実際にはありません。

おそらくそれはUFCSのように見えるかもしれませんか? 例: <() as Foo<()>> -裸のasようにタイプを変更せず、曖昧さを解消するだけです。 ::以上が続くことを想定しているため、これは現在無効な構文です。

Fn implTraitを使用した型推論に関する興味深いケースを見つけました。
次のコードは問題なくコンパイルされます。

fn op(s: &str) -> impl Fn(i32, i32) -> i32 {
    match s {
        "+" => ::std::ops::Add::add,
        "-" => ::std::ops::Sub::sub,
        "<" => |a,b| (a < b) as i32,
        _ => unimplemented!(),
    }
}

サブラインをコメントアウトすると、コンパイルエラーがスローされます。

error[E0308]: match arms have incompatible types
 --> src/main.rs:4:5
  |
4 | /     match s {
5 | |         "+" => ::std::ops::Add::add,
6 | | //         "-" => ::std::ops::Sub::sub,
7 | |         "<" => |a,b| (a < b) as i32,
8 | |         _ => unimplemented!(),
9 | |     }
  | |_____^ expected fn item, found closure
  |
  = note: expected type `fn(_, _) -> <_ as std::ops::Add<_>>::Output {<_ as std::ops::Add<_>>::add}`
             found type `[closure@src/main.rs:7:16: 7:36]`
note: match arm with an incompatible type
 --> src/main.rs:7:16
  |
7 |         "<" => |a,b| (a < b) as i32,
  |                ^^^^^^^^^^^^^^^^^^^^

error: aborting due to previous error

@oberienこれはimpl Traitは関係がないようです。これは、一般的な推論に当てはまります。 あなたの例のこのわずかな修正を試してください:

fn main() {
    let _: i32 = (match "" {
        "+" => ::std::ops::Add::add,
        //"-" => ::std::ops::Sub::sub,
        "<" => |a,b| (a < b) as i32,
        _ => unimplemented!(),
    })(5, 5);
}

これは現在閉鎖されているようです:

エリジオンと相互作用するときのICE

この号やディスカッションに記載されていないことの1つは、呼び出し元から提供されていないクロージャとジェネレータを構造体フィールドに格納する機能です。 現在、これは可能ですが、見た目は醜いです。各クロージャー/ジェネレーターフィールドの構造体に型パラメーターを追加してから、コンストラクター関数のシグネチャで、その型パラメーターをimpl FnMut/impl Generatorに置き換えます。 これが例です、そしてそれはかなりクールです! しかし、それは望まれることがたくさん残っています。 typeパラメータを取り除くことができれば、はるかに良いでしょう:

struct Counter(impl Generator<Yield=i32, Return=!>);

impl Counter {
    fn new() -> Counter {
        Counter(|| {
            let mut x: i32 = 0;
            loop {
                yield x;
                x += 1;
            }
        })
    }
}

impl Traitは、これを行う正しい方法ではない可能性があります。RFC2071を正しく読んで理解していれば、おそらく抽象型です。 必要なのは、実際の型( [generator@src/main.rs:15:17: 21:10 _] )を推測できるように構造体定義に記述できるものです。

@mikeyhew抽象型は、確かに私たちが期待する方法であると私は信じています。 構文はおおよそ次のようになります

abstract type MyGenerator: Generator<Yield = i32, Return = !>;

pub struct Counter(MyGenerator);

impl Counter {
    pub fn new() -> Counter {
        Counter(|| {
            let mut x: i32 = 0;
            loop {
                yield x;
                x += 1;
            }
        })
    }
}

私が構造に入れたいのが他の誰かのimpl Generatorあるが、彼らが私が使用するためのabstract typeを作成しなかった場合、フォールバックパスはありますか?

@scottmcmあなたはまだあなた自身のabstract type宣言することができます:

// library crate:
fn foo() -> impl Generator<Yield = i32, Return = !> { ... }

// your crate:
abstract type MyGenerator: Generator<Yield = i32, Return = !>;

pub struct Counter(MyGenerator);

impl Counter {
    pub fn new() -> Counter {
        let inner: MyGenerator = foo();
        Counter(inner)
    }
}

@cramertj待ってください、抽象型はすでに毎晩ありますか?! PRはどこにありますか?

@alexregいいえ、そうではありません。

編集:ご挨拶、未来からの訪問者! 以下の問題は解決されました。


#47348に登場するこのファンキーなエッジケースに注目したい

use ::std::ops::Sub;

fn test(foo: impl Sub) -> <impl Sub as Sub>::Output { foo - foo }

このようなimpl Trait予測を返すことも許可されるべきですか? (現在、__それは.__であるため)

このような使用法についての議論は見つかりませんでしたし、そのテストケースも見つかりませんでした。

@ExpHPうーん。 impl Foo<impl Bar>が問題になるのと同じ理由で、問題があるように見えます。 基本的に、問題のタイプに実際の制約はありません。タイプから投影されるものだけに

implsの「制約付き型パラメーター」に関するロジックを再利用したいと思います。 つまり、リターンタイプを指定すると、 impl Subが「制約」されるはずです。 私が参照している関数はこれです:

https://github.com/rust-lang/rust/blob/a0dcecff90c45ad5d4eb60859e22bb3f1b03842a/src/librustc_typeck/constrained_type_params.rs#L89 -L93

チェックボックスが好きな人のためのちょっとしたトリアージ:

  • 46464完了->チェックボックス
  • 48072完了->チェックボックス

@rfcbotfcpマージ

conservative_impl_traitとuniversal_impl_trait機能を安定させ、1つの変更を保留することを提案します(https://github.com/rust-lang/rust/issues/46541の修正)。

現在のセマンティクスを文書化するテスト

これらの機能のテストは、次のディレクトリにあります。

run-pass / impl-trait
ui / impl-trait
compile-fail / impl-trait

実装中に解決された質問

impl Traitの解析の詳細は、 RFC 2250で解決され、 https://github.com/rust-lang/rust/pull/45294で実装されました

impl Traitは、あいまいさを防ぐために、ネストされた非関連付けタイプの位置と特定の修飾されたパスの位置から禁止されています。 これはhttps://github.com/rust-lang/rust/pull/48084で実装されました

残りの不安定な機能

この安定化の後、引数の位置と非特性関数の戻り位置でimpl Traitを使用できるようになります。 ただし、将来の設計の反復を可能にするために、 Fn構文のどこでもimpl Traitを使用することは引き続き許可されていません。 さらに、引数の位置でimpl Traitを使用する関数の型パラメーターを手動で指定することはできません。

チームメンバーの@cramertjは、これをマージすることを提案しました。 次のステップは、タグ付けされた残りのチームによるレビューです。

  • [x] @aturon
  • [x] @cramertj
  • [x] @eddyb
  • [x] @nikomatsakis
  • [x] @nrc
  • [x] @pnkfelix
  • [x] @withoutboats

現在リストされている懸念はありません。

レビューアの過半数が承認すると(そして反対はありません)、これは最終コメント期間に入ります。 このプロセスのどの時点でも提起されていない大きな問題を見つけた場合は、声を上げてください。

タグ付けされたチームメンバーが私に与えることができるコマンドについては、このドキュメントを参照してください。

この安定化の後、非トレイト関数の引数位置と戻り位置でimplトレイトを使用できるようになります。 ただし、将来の設計の反復を可能にするために、Fn構文のどこでもimplTraitを使用することは引き続き許可されていません。 さらに、引数の位置でimplTraitを使用する関数の型パラメーターを手動で指定することはできません。

特性関数の引数/戻り位置、またはFn構文でimpl Traitを使用する状況はどうなっていますか?

@alexreg Return-トレイト内のimpl Trait位置は、RFCでブロックされますが、RFC 2071は、実装されると同様の機能を許可します。 特性内の引数位置impl Traitは、私が知っている技術的機能ではブロックされていませんが、RFCで明示的に許可されていないため、当面は省略されています。

impl Trait Fn構文の引数位置にあるT: Fn(impl Trait)をT: for<X: Trait> Fn(X)デシュガーする必要があると考える人もいるためです。 impl Trait Fn構文の戻り位置にあるimpl Traitは、私が知っている技術的な理由でブロックされていませんが、RFCでは、さらなる設計作業が行われるまで許可されていませんでした。これを安定させる前に、別のRFCまたは少なくとも別のFCPを参照してください。

@cramertjわかりました、更新していただきありがとうfoo: Tに意味があります。ここで、 T: Traitは、私が間違っていない限り、 foo: impl Traitと同等です。

懸念事項: https ://github.com/rust-lang/rust/issues/34511#issuecomment-322340401は引き続き同じです。 以下を許可することは可能ですか?

fn do_it_later_but_cannot() -> impl Iterator<Item=u8> { //~ ERROR E0227
    unimplemented!()
}

@kennytmいいえ、現時点では不可能です。 その関数は!返しますが、これは指定した特性を実装しておらず、適切な型に変換するメカニズムもありません。 これは残念なことですが、現時点で修正する簡単な方法はありません( !さらに多くの特性を実装することを除けば)。 また、将来修正するための下位互換性もあります。これを機能させると、厳密に多くのコードをコンパイルできるようになるためです。

ターボフィッシュの質問は半分しか解決されていません。 引数のimpl Traitがパブリックチェックの新しいimpl Traitについて少なくとも警告する必要があります。

動機は、引数を明示的なジェネリックからimpl Trait変更することにより、ライブラリがユーザーのターボフィッシュを壊さないようにすることです。 重大な変更であるかどうかをライブラリが知るための優れたリファレンスガイドはまだありません。テストでこれが検出される可能性はほとんどありません。 この問題については十分に議論されていませんでした。完全に決定する前に安定させたい場合は、少なくとも銃をlib作成者の足元から遠ざける必要があります。

動機は、引数を明示的なジェネリックからimpl Trait変更することにより、ライブラリがユーザーのターボフィッシュを壊さないようにすることです。

これが起こり始め、人々が現在疑わしいlang-teamの人々が不平を言い始めるとき、 impl Traitがターボフィッシュで型引数を明示的に提供することをサポートするべきであると確信することを願っています。

@leodasvacas

ターボフィッシュの質問は半分しか解決されていません。 引数のimplTraitは、パブリックチェックの新しいプライベートのプライベートタイプであると考えて、少なくとも、効果的にパブリック関数の引数のimplTraitについて警告する必要があります。

私は同意しません-これは解決されました。 当面、これらの機能のためにターボフィッシュを完全に禁止します。 明示的なジェネリックパラメーターの代わりにimpl Traitを使用するようにパブリック関数のシグネチャを変更することは、重大な変更です。

将来、これらの関数にターボフィッシュを許可する場合、 impl Traitタイプのパラメーターのみを指定できるようになる可能性があります。

:bell:上記のこれは現在、最終コメント期間に入っています。 :ベル:

https://github.com/rust-lang/rust/pull/49041が着陸するまで安定させたくないことを付け加えておきます。 (しかし、うまくいけば、それはすぐになります。)

したがって、#49041には#46541の修正が含まれていますが、その修正は私が予想したよりも大きな影響を及ぼします(たとえば、コンパイラは現在ブートストラップされません)。これにより、ここで正しいコースについて一時停止することができます。 #49041の問題は、想定外のライフタイムが誤ってリークする可能性があることです。 これがコンパイラでどのように現れるかを次に示します。 このような方法があるかもしれません:

impl TyCtxt<'cx, 'gcx, 'tcx>
where 'gcx: 'tcx, 'tcx: 'cx
{
    fn foos(self) -> impl Iterator<Item = &'tcx Foo> + 'cx {
        /* returns some type `Baz<'cx, 'gcx, 'tcx>` that captures self */
    }
}

ここで重要なことは、 TyCtxtは'tcxと'gcxで不変であるため、リターンタイプに表示される必要があるということです。 それでも、 'cxと'tcxのみがimplトレイトの境界に表示されるため、これら2つのライフタイムのみが「キャプチャ」されることになっています。 古いコンパイラは'gcx: 'cxためにこれを受け入れていましたが、私たちが考えている脱糖について考えると、それは実際には正しくありません。 その脱糖は、次のような抽象型を作成します。

abstract type Foos<'cx, 'tcx>: Iterator<Item = &'tcx Foo> + 'cx;

それでも、この抽象型の値はBaz<'cx, 'gcx, 'tcx>が、 'gcxはスコープに含まれていません。

ここでの回避策は、境界内に'gcxという名前を付ける必要があることです。 これはやっかいなことです。 'cx + 'gcxは使用できません。 ダミーの特性を作成すると仮定できます。

trait Captures<'a> { }
impl<T: ?Sized> Captures<'a> for T { }

次に、このimpl Iterator<Item = &'tcx Foo> + Captures<'gcx> + Captures<'cx>ようなものを返します。

注意し忘れたことがあります。宣言されたリターンタイプがdyn Iterator<Item = &'tcx Foo> + 'cx場合、 dynタイプは有効期間を消去すると予想されるため、問題ありません。 したがって、 dyn Trait不可能なimpl Traitで問題のあることは何もできないと仮定して、ここで不健全が発生する可能性はないと思います。

抽象型の値が同様の実存的であるという考えを漠然と想像することができます: exists<'gcx> Baz<'cx, 'gcx, 'tcx> 。

ただし、保守的なサブセット(上記のfnsを除外する)を安定させ、後でどのように考えたいかを決定したら、これを拡張の可能性として再検討することは問題ないようです。

更新: dyn特性についての私の意味を明確にするために:境界(ここでは'cx )がそれを保証する限り、 'gcxようなライフタイムを「隠す」ことができると言っています'gcx dyn Traitが使用されている場所では、 'gcxは引き続き有効です。

@nikomatsakisこれは興味深い例ですが、ここでの基本的な計算が変わるとは思いません。つまり、関連するすべてのライフタイムをリターンタイプだけから明確にする必要があります。

Captures特性は、この状況に適した軽量のアプローチのようです。 今のところ不安定なstd::markerに入る可能性があるようですか?

@nikomatsakisあなたのフォローアップコメントは、この場合に'gcxを削除することを期待する理由を理解するために、ここですべての部分を完全にまとめていなかったことに気づきました。つまり、 'gcxはクライアントの観点から見た「関連する生涯」。 いずれにせよ、保守的なスタートは問題ないようです。

私の個人的な意見では、 https://github.com/rust-lang/rust/issues/46541は実際にはバグではありませんTraitを実装し、ライフタイム'aをimpl Trait + 'aとして存続させるタイプを、他のライフタイムに関係なく返すことができるはずです。 ただし、@ rust-lang / langが好むのであれば、より保守的なアプローチを安定させて開始することは問題ありません。

(もう1つ明確にする必要があります。#49041の修正でエラーが発生するのは、非表示の型が欠落しているライフタイム'gcxに関して不変である場合のみであるため、これが発生することは比較的まれです。)

@cramertj

私の個人的な意見では、#46541は実際にはバグではありません。これは私が期待する動作であり、どのように不健全にすることができるかわかりません。回避するのは面倒です。

私はそのPOVに同情していますが、それを脱糖する方法がわからないものを安定させるのは気が進まない(たとえば、実存的な生涯の漠然とした概念に依存しているように見えるため)。

@rfcbotは複数の返品サイトに関係します

私は、実存的なimpl特性に関する最後の懸念を1つ登録したいと思います。 implトレイトを使用したい時間のかなりの部分で、実際には複数のタイプを返したいと思っています。 例えば:

fn foo(empty: bool) -> impl Iterator<Item = u32> {
    if empty { None.into_iter() } else { &[1, 2, 3].cloned() }
}

もちろん、これは今日は機能しません。それを機能させることは間違いなく範囲外です。 ただし、 implトレイトが現在機能する方法では、(その構文で)これまで機能するための扉を効果的に閉じています。 これは、現在、複数の返品サイトから制約を蓄積できるためです。

fn foo(empty: bool) -> (impl Debug, impl Debug) {
    if empty { return (22, Default::default()); }
    return (Default::default(), false);
}

ここで、推測される型は(i32, bool)で、最初のreturnはi32部分を制約し、2番目のreturn bool部分を制約します。

これは、2つのreturnステートメントが統合されない場合(私の最初の例のように)をサポートできないことを意味します。そうでない場合、統合するのは非常に面倒です。

各return (通常、制約の各ソース)を個別に完全に指定する必要がある制約を設定する必要があるのでしょうか。 (そして、事後にそれらを統合しますか?)

これは私の2番目の例を違法にし、将来のある時点で最初のケースをサポートする余地を残します。

@rfcbotは複数のリターンサイトを解決します

だから私は#rust-langで@cramertjとました。 impl Traitで「アーリーリターン」を不安定にするというアイデアについて話し合っていたので、最終的には変更する可能性があります。

彼らは、この種の構文に明示的にオプトインする方がよいと主張しました。特に、それが必要な他のケース( let x: impl Trait = if { ... } else { ... } )があり、期待できないためです。それらすべてを暗黙的に処理します(絶対にそうではありません)。

これはかなり説得力があると思います。 これ以前は、とにかくここでオプトイン構文を使用することを想定していましたが、ドアを時期尚早に閉じないようにしたかっただけです。 結局のところ、「ダイナミックシム」をいつ挿入する必要があるかを説明するのはちょっと難しいです。

@nikomatsakis私のおそらくあまり知られていない意見:実行時に可能な複数の型の1つを返す関数を有効にすることは有用ですが、単一の型への静的な戻り型の推論と、内部で実行時の決定が必要な状況を許可します(単に「動的シム」と呼んでいます)。

その最初のfoo例は、私が問題を理解している限り、(1)ボックス化された+型消去されたIterator<Item = u32> 、または(2)合計型std::option::Iterいずれかに解決できます。 std::slice::Iterは、 Iterator実装を導き出します。 議論にいくつかの更新があり(つまり、私は今IRCログを読んでいます)、それを理解するのが難しくなっているので、短くしようとしています:私は動的シムのdynのような構文に確かに同意しますが、私もdynと呼ぶのは理想的ではないかもしれないことを理解してください。

恥知らずなプラグと記録のための小さなメモ:「匿名」の合計タイプと製品を簡単に入手できます。

  • Coprod: https ://docs.rs/frunk_core/0.0.23/frunk_core/coproduct/trait.CoprodInjector.html
  • HList: https ://docs.rs/frunk_core/0.0.23/frunk_core/hlist/index.html

@Centrilええ、CoprodInjector::injectが機能するためには、結果の型が推測可能である必要があることに注意してください。これは通常、結果の型に名前を付けないと不可能です(たとえば、 -> Coprod!(A, B, C) )。 名前のないタイプで作業していることがよくあるので、 -> Coprod!(impl Trait, impl Trait, impl Trait)必要になります。これは、どのバリアントにどのimpl Traitタイプが含まれるべきかわからないため、推論に失敗します。

@cramertj非常に正しい(補足:各「バリアント」は完全に名前を付けられないわけではありませんが、部分的にのみです。例: Map<Namable, Unnameable> )。

enum impl Traitアイデアは、 https://internals.rust-lang.org/t/pre-rfc-anonymous-enums/5695で以前に説明されてい

@Centrilええ、それは本当です。 私は特に先物について考えています。

fn foo(x: Foo) -> impl Future<Item = (), Error = Never> {
    match x {
        Foo::Bar => do_request().and_then(|res| ...).left().left(),
        Foo::Baz => do_other_thing().and_then(|res| ...).left().right(),
        Foo::Boo => do_third_thing().and_then(|res| ...).right(),
    }
}

@cramertj匿名の列挙型がenum impl Traitに似ているとは言えません。これは、 X: Tr && Y: Tr ⇒ (X|Y): Tr (反例: Default結論付けることができないためです。 impl Future for (X|Y|Z|...)を作成する必要があります。

@kennytmおそらく、匿名列挙型のいくつかのトレイトimplを自動生成したいので、基本的に同じ機能のようです。

匿名の列挙型は名前を付けることができますので@cramertj(あわや)場合、 Default IMPLがために生成される(i32|String) 、我々は書くことができるだろう<(i32|String)>::default() 。 OTOH <enum impl Default>::default()単にコンパイルされないので、何を自動生成しても、まったく呼び出せないので安全です。

それでも、自動生成によってenum impl Trait問題が発生する場合があります。 検討

pub trait Rng {
    fn next_u32(&mut self) -> u32;
    fn gen<T: Rand>(&mut self) -> T where Self: Sized;
    fn gen_iter<'a, T: Rand>(&'a mut self) -> Generator<'a, T, Self> where Self: Sized;
}

mut rng: (XorShiftRng|IsaacRng)がある場合、 rng.next_u32()またはrng.gen::<u64>()計算できるのはまったく正常なことです。 ただし、自動生成では(Generator<'a, u16, XorShiftRng>|Generator<'a, u16, IsaacRng>)しか生成できないため、 rng.gen_iter::<u16>()を構築できませんが、実際に必要なのはGenerator<'a, u16, (XorShiftRng|IsaacRng)>です。

(たぶん、コンパイラは、 Sizedチェックのように、委任が安全でない呼び出しを自動的に拒否できます。)

FWIWこの機能は、タプル(もちろん、仮想の匿名enum対応する匿名のstructよりもクロージャーに精神的に近いと私は思います。 これらのものが「匿名」である方法は異なります。

匿名のstructとenum (タプルと「分離」)の場合、「匿名」は「構造的」(「名目」ではなく)タイプの意味です。再組み込みされ、コンポーネントタイプに対して完全に汎用的であり、ソースファイルの名前付き宣言ではありません。 しかし、プログラマーはそれでもそれらを書き出して他のタイプと同じように使用し、それらのトレイト実装は通常どおり明示的に書き留められ、特に魔法ではありません(組み込みの構文と「可変個引数」であることを除けば、他のタイプまだできません)。 ある意味では、彼らは名前を持っています

一方、クロージャーは、その名前が秘密であるという意味で匿名です。 コンパイラーは、作成するたびに新しい名前の新しい型を生成します。その名前が何であるかを調べたり、必要な場合でも参照したりする方法はありません。 コンパイラーは、このシークレット型に対して1つまたは2つの特性を実装します。コンパイラーと対話できる唯一の方法は、これらの特性を使用することです。

impl Trait背後にあるifさまざまなブランチからさまざまな型を返すことができるのは、後者に近いようです-コンパイラは、さまざまなブランチを保持する型を暗黙的に生成し、要求された特性を実装します適切なものにディスパッチするためにその上にあり、プログラマーはそのタイプが何であるかを書き留めたり見たりすることはなく、それを参照することも、したい本当の理由もありません。

(実際、この機能は、仮想の「オブジェクトリテラル」に関連しているように感じます。これは、他の特性の場合、既存のクロージャ構文がFn場合と同じです。つまり、単一のラムダ式の代わりに、スコープ内の変数を使用して、指定された特性の各メソッドを実装し( selfは暗黙的)、コンパイラーはupvarsを保持する匿名型を生成し、指定された特性を実装します。同じようにオプションのmoveモードがあります。とにかく、 if foo() { (some future) } else { (other future) }を表現する別の方法はobject Future { fn poll() { if foo() { (some future).poll() } else { (other future).poll() } } }になると思います(まあ、あなたも必要です結果持ち上げるためにfoo()に出をletそれは一度だけ実行されると思いますので)は。むしろ少ない人間工学的だと、おそらく他に

@glaebhoerlそれは非常に興味深いアイデアです! ここには、Javaの先行技術もいくつかあります。

私の頭のてっぺんからのいくつかの考え(それほど焼かれていません):

  1. [bikeshed]接頭辞objectは、これが単なる存在ではなく特性オブジェクトであることを示していますが、そうではありません。

可能な代替構文:

impl Future { fn poll() { if foo() { a.poll() } else { b.poll() } } }
// ^ --
// this conflicts with inherent impls for types, so you have to delay
// things until you know whether `Future` is a type or a trait.
// This might be __very__ problematic.

// and perhaps (but probably not...):
dyn Future { fn poll() { if foo() { a.poll() } else { b.poll() } } }
  1. [マクロ/砂糖]次のように、簡単な構文糖衣を提供できます。
future!(if foo() { a.poll() } else { b.poll() })

ええ、構文の質問は混乱しています。 structリテラル、クロージャ、またはimplブロックからインスピレーションを得たいかどうかが明確ではないからです:)たとえば、頭のてっぺんから1つ選んだだけです。酒。 (とにかく、私の主なポイントは、オブジェクトリテラルを追加する必要があるということではありませんでしたが、匿名のenumは、ここでは赤いニシンだと思います[ただし、追加する必要があります]。)

ifの異なるブランチから異なるタイプを返すことができることは、implトレイトの背後で、後者に近いように見えます-コンパイラは、異なるブランチを保持するタイプを暗黙的に生成し、要求されたトレイトを実装して適切なトレイトにディスパッチします、そして、プログラマーはそのタイプが何であるかを書き留めたり見たりすることは決してなく、それを参照することも、したい本当の理由もありません。

うーん。 したがって、列挙型の「新しい名前」を生成するのではなく、次のようなimplに対応する|型を利用すると想定していました。

impl<A: IntoIterator, B: IntoIterator> IntoIterator for (A|B)  { /* dispatch appropriately */ }

複数の関数が同一のimplを生成するという意味で、これには明らかに一貫性の問題があります。 しかし、それらを脇に置いても、このアイデアは他の理由で機能しない可能性があることに気付きました。たとえば、関連するタイプが複数ある場合、コンテキストによっては同じである必要がありますが、異なる場合もあります。 たとえば、次のようになります。

-> impl IntoIterator<Item = Y>

しかし、どこか他の場所で

-> impl IntoIterator<IntoIter = X, Item = Y>

これらは、「合体」できないと私が推測する2つの重複する実装になります。 まあ、多分専門で。

とにかく、「秘密の列挙型」の概念は、私が思うにすべての周りでよりきれいに見えます。

私は、実存的なimpl特性に関する最後の懸念を1つ登録したいと思います。 implトレイトを使用したい時間のかなりの部分で、実際には複数のタイプを返したいと思っています。

@nikomatsakis:それは、この場合には、何が返されていることを言うことは公正が近づいすることにあるdyn Traitよりimpl Trait合成/匿名の戻り値を実装しているので、ダイナミックディスパッチに似何か、?

cc https://github.com/rust-lang/rust/issues/49288 、最近、 FutureとFuture使用して、多くの問題に直面しています。

これはFCPが終了する前の最後のチャンスなので、自動自動特性に対して最後の議論をしたいと思います。 これは少しぎりぎりのことだと思いますので、現在の実装にコミットする前に、せいぜいこの問題に正式に対処したいと思います。

impl Traitフォローしていない人のために明確にするために、これは私が提示している問題です。 impl X型で表される型は、現在、その背後にある具体的な型が自動特性を実装している場合にのみ、自動特性を自動的に実装します。 具体的には、次のコード変更が行われた場合、関数はコンパイルを続行しますが、返される型がSendを実装しているという事実に依存する関数の使用は失敗します。

 fn does_some_operation() -> impl Future<Item=(), Error=()> {
-    let data_stored = Arc::new("hello");
+    let data_stored = Rc::new("hello");

     return some_long_operation.and_then(|other_stuff| {
         do_other_calculation_with(data_stored)
     });
}

(より簡単な例:動作中、内部変更により障害が発生します)

この問題は明確ではありません。 自動特性を「リーク」させるという非常に慎重な決定がありました。そうでない場合は、送信または同期以外の何かを返すすべての関数に+ !Send + !Syncを設定する必要があります。関数が返す具体的なタイプに単純に実装できない可能性のある他のカスタム自動特性についての不明確な話があります。 これらは、後で触れる2つの問題です。

まず、この問題に対する私の反対意見を簡単に述べたいと思います。これにより、関数本体を変更して、公開されているAPIを変更できます。 これにより、コードの保守性が直接低下します。

さびの発生を通して、使いやすさよりも冗長性の側で誤りを犯す決定がなされてきました。 新規参入者がこれらを見ると、冗長性のために冗長性であると考えますが、そうではありません。 構造体にコピーを自動的に実装させないか、関数シグネチャですべての型を明示的にするかどうかにかかわらず、各決定は保守性のためです。

私が人々にRustを紹介するとき、確かに、私は彼らにスピード、生産​​性、メモリの安全性を示すことができます。 しかし、行くにはスピードがあります。 エイダはメモリ安全性があります。 Pythonには生産性があります。 Rustが持っているものはこれらすべてに勝っており、保守性があります。 ライブラリの作成者がアルゴリズムをより効率的に変更したい場合、またはクレートの構造をやり直したい場合は、間違いを犯したときに通知するという強力な保証がコンパイラからあります。 さびたところで、私のコードはメモリの安全性だけでなく、ロジックとインターフェイスの観点からも機能し続けると確信できます。 _Rustのすべての関数インターフェースは、関数の型宣言で完全に表現できます_。

impl Traitそのまま安定させることは、この信念に反する可能性が高いです。 確かに、コードをすばやく作成するためには非常に便利ですが、プロトタイプを作成する場合はPythonを使用します。 Rustは、短期間の書き込み専用コードではなく、長期的な保守性が必要な場合に選択される言語です。


ここでも、問題が明確ではないため、これが悪い可能性は「大きい」と言えます。 そもそも「自動特性」の全体的な考え方は非明示的です。 SendとSyncは、パブリック宣言ではなく、構造体の内容に基づいて実装されます。 この決定はさびのためにうまくいったので、同様に行動するimpl Traitもうまくいく可能性があります。

ただし、関数と構造はコードベースで異なる方法で使用され、これらは同じ問題ではありません。

構造体のフィールドを変更すると、プライベートフィールドであっても、実際の内容を変更していることがすぐにわかります。 非送信または非同期フィールドを持つ構造はその選択を行い、ライブラリのメンテナは、PRが構造のフィールドを変更するときに再確認することを知っています。

関数の内部を変更する場合、パフォーマンスと正確さの両方に影響を与える可能性があることは明らかです。 ただし、Rustでは、正しいタイプが返されていることを確認する必要はありません。 関数宣言は私たちが守らなければならない厳しい契約であり、 rustcは私たちの後ろを監視します。 これは、構造体の自動特性と関数の戻り値の間の細い線ですが、関数の内部を変更することははるかに日常的です。 ジェネレーターを利用した完全なFutureができたら、 -> impl Future返す関数を変更するのがさらに日常的になります。 これらはすべて、コンパイラがそれをキャッチしない場合、変更された送信/同期実装について作成者がスクリーニングする必要がある変更になります。

これを解決するために、元のRFCの説明とと判断できます。 保守的なimpl特性RFCのこのセクションでは、自動特性のリークに関する最大の議論を示しています(「OIBIT」は自動特性の古い名前です)。

これに対する私の主な回答はすでに説明しましたが、最後にもう1つ注意しておきます。 構造のレイアウトを変更することはそれほど一般的ではありません。 それを防ぐことができます。 関数が同じ自動特性を実装し続けることを保証するための保守の負担は、関数が大幅に変更されるという理由だけで、構造のそれよりも大きくなります。


最後に、自動自動特性だけが選択肢ではないことをお伝えしたいと思います。 これは私たちが選択したオプションですが、オプトアウトの自動特性の代替手段は依然として代替手段です。

非送信/非同期アイテムを返す関数に、状態+ !Send + !Syncか、それらの境界を持つ特性(別名?)を返すように要求することができます。 これは良い決定ではありませんが、現在選択しているものよりも良いかもしれません。

カスタム自動特性に関する懸念については、新しい自動特性は実装されるべきではないと主張します。自動特性の後に導入された新しいタイプに対してのみ実装されます。 これは、私が現在対処できるよりも多くの問題を提供する可能性がありますが、より多くの設計で対処できない問題ではありません。


これは非常に遅く、非常に長い間巻き込まれており、私は以前にこれらの異議を唱えたことがあると確信しています。 最後にもう一度コメントできてうれしいです。そして、私たちが下している決定に完全に満足していることを確認します。

読んでいただきありがとうございます。最終決定により、Rustが最善の方向に進むことを願っています。

@daborossのレビュー

trait FutureNSS<T, E> = Future<Item = T, Error= E> + !Send + !Sync;

fn does_some_operation() -> impl FutureNSS<(), ()> {
     let data_stored = Rc::new("hello");
     some_long_operation.and_then(|other_stuff| {
         do_other_calculation_with(data_stored)
     });
}

これはそれほど悪くはありません。適切な名前を考え出す必要があります( FutureNSSはそうではありません)。 主な利点は、境界の繰り返しによって発生する紙切れを減らすことです。

自動特性を明示的に述べる要件でこの機能を安定させ、後でそのメンテナンスの問題に対する適切な解決策を見つけたら、または実際にメンテナンスの負担がないことが十分にわかったら、それらの要件を削除することはできませんか?要件を解除する決定?

!Sendとしてマークされていない限りSend要求し、同期としてマークされていない限りSyncないのはどうですか? 送信は同期と比較してより一般的であると思われませんか?

このような:

fn provides_send_only1() -> impl Trait {  compatible_with_Send_and_Sync }
fn provides_send_only2() -> impl Trait {  compatible_with_Send_only }
fn fails_to_complile1() -> impl Trait {  not_compatible_with_Send }
fn provides_nothing1() -> !Send + impl Trait { compatible_with_Send}
fn provides_nothing2() -> !Send + impl Trait { not_compatible_with_Send }
fn provides_send_and_sync() -> Sync + impl Trait {  compatible_with_Send_and_Sync }
fn fails_to_compile2() -> Sync + impl Trait { compatible_with_Send_only }

引数位置のimpl Traitと戻り位置wrtの間に矛盾がありますか? 自動特性?

fn foo(x: impl ImportantTrait) {
    // Can't use Send cause we have not required it...
}

これは、ここで送信を想定することが許可されている場合、引数の位置の原因として意味があります。モノモルフィゼーション後のエラーが発生します。 もちろん、ここでは戻り位置と引数位置の規則が一致している必要はありませんが、学習可能性の点で問題があります。

カスタム自動特性に関する懸念については、新しい自動特性は実装されるべきではないと主張します。自動特性の後に導入された新しいタイプに対してのみ実装されます。

まあ、これは次の自動トレイトUnpin (自己参照ジェネレーターには実装されていない)にも当てはまりますが...それは運が悪いようですか? これは私たちが本当に生きることができる制限ですか? 将来、たとえば&mutやRcで無効にする必要のあるものがなくなるとは信じられません...

私はこれが議論されたと信じています、そしてこれはもちろん非常に遅いです、しかし私はまだ議論の位置にimpl Traitに満足していません。

値によって閉鎖/先物の両方a)の仕事に能力、およびb)は、「出力」ので、実装の詳細として、いくつかの種類を扱う慣用的であり、彼らは直接、性能、安定性の錆のコアバリューをサポートしているため、1.0以前からされていますと安全性。

したがって、 -> impl Traitは、1.0による約束を果たすか、エッジケースを削除するか、既存の機能を一般化するだけです。これは、匿名型の処理に常に使用されているのと同じメカニズムを使用して、関数に出力型を追加し、それを適用します。多くの場合。 abstract type 、つまりモジュールの出力タイプから始める方が原則的だったかもしれませんが、RustにMLモジュールシステムがないことを考えると、順序は大したことではありません。

fn f(t: impl Trait)代わりに「できるという理由だけで」追加されたように感じ、見返りに十分な恩返しをせずに言語をより大きく、見知らぬものにします。 私は苦労し、それに適合する既存のフレームワークを見つけることができませんでした。 fn f(f: impl Fn(...) -> ...)の簡潔さに関する議論と、境界がすでに<T: Trait>とwhere両方の句に含まれている可能性があることの正当性を理解していますが、それらは中空に感じます。 彼らは欠点を否定しません:

  • ここで、境界の2つの構文を学習する必要があります。少なくとも<> / whereは1つの構文を共有します。

    • これはまた、学習の崖を作成し、複数の場所で同じ汎用タイプを使用するという考えを覆い隠します。

    • 新しい構文では、関数が一般的であるかどうかを判断するのが難しくなります。引数リスト全体をスキャンする必要があります。

  • あなたはその型を書き込むことができないので、今どのような機能の実装の詳細でなければなりませんが(それはその型パラメータを宣言する方法)、そのインタフェースの一部になります!

    • これは、現在議論されている自動特性の複雑さにも関係しています。関数のパブリックインターフェイスとは何か、そうでないものはさらに混乱しています。

  • dyn Traitとの類似性は、正直なところ、誤ったものです。

    • dyn Trait常に同じことを意味し、既存の自動トレイトメカニズムを介する以外は、周囲の宣言に「感染」しません。

    • dyn Traitはデータ構造で使用でき、これは実際にはその主要なユースケースの1つです。 データ構造のimpl Traitは、データ構造のすべての用途を調べなければ意味がありません。

    • dyn Trait意味の一部は型消去ですが、 impl Traitはその実装について何も意味しません。

    • 単形化されていないジェネリックを導入すると、前のポイントはさらに混乱します。 実際、このような状況では、 fn f(t: impl Trait)は、a)新機能では機能しない、および/またはb)自動特性の問題のようにさらに多くのエッジケースの弁護士を必要とする可能性があります。 fn f<dyn T: Trait>(t: T, u: dyn impl Urait)想像してみてください! :悲鳴:

つまり、引数の位置にあるimpl Traitはエッジケースを追加し、奇妙な予算をより多く使用し、言語をより大きく感じさせるなどですが、戻りの位置にあるimpl Traitは統一されます。簡素化し、言語をより緊密に結び付けます。

!Sendとしてマークされていない限りSendを要求し、Syncとしてマークされていない限りSyncを提供しないのはどうですか? 送信は同期と比較してより一般的であると思われませんか?

それは非常に…恣意的でアドホックな感じがします。 多分それはタイピングが少ないですが、より多くの記憶とより多くの混乱です。

バイクは小屋-yの考えをここにないように気をそらすに上記の私の点から:代わりにimpl 、使用type ? これは関連する型に使用されるキーワードであり、 abstract typeに使用されるキーワード(の1つ)である可能性が高く、それでもかなり自然であり、「関数の出力型」の概念をより示唆しています。

// keeping the same basic structure, just replacing the keyword:
fn f() -> type Trait

// trying to lean further into the concept:
fn f() -> type R: Trait
fn f() -> type R where R: Trait
fn f() -> (i32, type R) where R: Trait
// or perhaps:
fn f() -> type R: Trait in R
// or maybe just:
fn f() -> type: Trait

読んでいただきありがとうございます。最終決定により、Rustが最善の方向に進むことを願っています。

よく書かれた異議に感謝します。 あなたが指摘したように、自動特性は常に、隠されたままであると予想されたかもしれないいくつかの実装の詳細を「公開」するための意図的な選択でした。 これまでのところ、その選択は実際にはかなりうまくいったと思いますが、私は常にそれについて緊張していると告白します。

重要な問題は関数が実際に構造体異なっている程度であるように私には思えます。

構造のレイアウトを変更することはそれほど一般的ではありません。 それを防ぐことができます。 関数が同じ自動特性を実装し続けることを保証するための保守の負担は、関数が大幅に変更されるという理由だけで、構造のそれよりも大きくなります。

これがどれほど真実であるかを知るのは本当に難しいです。 Rc導入は慎重に行う必要があるというのが一般的なルールのようです。それは、どこに保存するかという問題ではRcではなく、 dyn Trait導入することです。これは、あまり明白ではない可能性があるためです。)

futuresを返すコードでは、スレッドセーフでない型などを操作することはまれであると強く思います。 あなたはそれらの種類のライブラリを避ける傾向があります。 (もちろん、現実的なシナリオでコードを実行するテストを行うことは常に有益です。)

いずれにせよ、安定化期間をいくら与えても、事前に知るのは難しいのでイライラします。

最後に、自動自動特性だけが選択肢ではないことをお伝えしたいと思います。 これは私たちが選択したオプションですが、オプトアウトの自動特性の代替手段は依然として代替手段です。

確かに、 Sendような特定の自動特性を「選び出す」という考えには間違いなく緊張しています。 また、先物以外にもimplトレイトのユースケースがあることにも留意してください。 たとえば、イテレータまたはクロージャを返す-そしてそのような場合、デフォルトで送信または同期するかどうかは明らかではありません。 いずれにせよ、あなたが本当に望んでいること、そして私たちが延期しようとしていること=)は、一種の「条件付き」バインドです( Tが送信の場合は送信)。 これはまさに自動特性があなたに与えるものです。

@rpjohnst

これは議論されたと思います

確かに、それは:)何年も前の最初のimpl Trait RFClo以来ずっとあります。 (すごい、2014年。私は年をとっています。)

私は苦労し、それに適合する既存のフレームワークを見つけることができませんでした。

まったく逆の感じがします。 私には、なしimpl Trait 、引数の位置にあるimpl Trait復帰位置では、すべてのより多くを際立っています。 私が見る統一スレッドは次のとおりです。

  • impl Trait -表示される場所には、「 Traitを実装するいくつかの単形化された型」があることを示しています。 (誰がそのタイプ(発信者または着信者)を指定するかは、 impl Traitが表示される場所によって異なります。)
  • dyn Trait -表示されている場所は、 Traitを実装するタイプがあることを示していますが、タイプの選択は動的に行われています。

その直感に基づいて、 impl Traitが表示される場所のセットを拡張する計画もあります。 たとえば、 https://github.com/rust-lang/rfcs/pull/2071は許可します

let x: impl Trait = ...;

同じ原則が適用されます。タイプの選択は静的に知られています。 同様に、同じRFCでabstract type ( impl Traitは一種の合成糖として理解できます)が導入されています。これは、トレイトimplに表示され、モジュールのメンバーとしても表示されます。

バイクは小屋-yの考えをここにそうではないそらすとしての私の点から上:代わりにimpl 、使用type ?

個人的には、ここで自転車小屋を復活させる気はありません。 https://github.com/rust-lang/rfcs/pull/2071などで構文について議論するのにかなりの時間を費やしました。 「完璧なキーワード」はないようですが、 implを「実装するタイプ」として読み取ると、非常にうまく機能します。

自動トレイトリークについてもう少し追加しましょう:

まず、最終的には、自動トレイトリークは実際にはここで行うのが正しいことだと思います。これは、他の言語と一貫しているからです。 自動特性は、前に述べたように、常にギャンブルでしたが、基本的には成果を上げているようです。 impl Traitがそれほど大きく異なるとは思えません。

しかしまた、私はここで遅れることについてかなり緊張しています。 私は他の興味深い点は、設計空間であることに同意し、私たちは右のスポットを直撃している100%の自信を持っていないが、私は、私たちが今までそれの確実であることを知りません。 今遅れると、今年のロードマップを実現するのに苦労するのではないかとかなり心配しています。

最後に、私が間違っている場合の影響について考えてみましょう。ここで基本的に話しているのは、semverの判断がさらに微妙になるということです。 これは懸念事項だと思いますが、さまざまな方法で軽減できる問題です。 たとえば、 !Sendまたは!Syncタイプが導入されたときに警告するリントを使用できます。 偶発的なsemver違反を防ぐのに役立つsemvercheckerの導入については、長い間話し合ってきました。これは、それが役立つ別のケースのようです。 要するに、問題ですが、私は重大な問題ではないと思います。

ですから、少なくとも現時点では、私はまだ現在の道を歩み続けたいと思っています。

個人的には、ここで自転車小屋を復活させる気はありません。

私もそれにひどく投資していません。 引数の位置にあるimpl Traitは、意味的にではなく

私には、なしimpl Trait 、引数の位置にあるimpl Trait復帰位置では、すべてのより多くを際立っています。

関連する型との類似性を考えると、これは「引数の位置にtype Tがないと、関連する型がさらに目立つ」のようになります。 私たちが選択した構文は無意味に感じさせるため、特定の異議は出ていないのではないかと思います。既存の構文は、 trait Trait<type SomeAssociatedType>ような構文糖衣の必要性を誰も感じないほど十分です。

「 Traitを実装するいくつかの単形化された型」の構文はすでにあります。 トレイトの場合、「呼び出し元」と「呼び出し先」の両方で指定されたバリアントがあります。 関数の場合、呼び出し元が指定したバリアントしかないため、呼び出し先が指定したバリアントの新しい構文が必要です。

この新しい構文をローカル変数に拡張することは正当化される可能性があります。これは、関連付けられたタイプのような状況でもあるためです。これは、式の出力タイプを非表示にして名前を付ける方法であり、呼び出し先関数の出力タイプを転送するのに役立ちます。

以前のコメントで述べたように、私はabstract typeファンでもあります。 繰り返しになりますが、これは単に「出力タイプ」の概念をモジュールに拡張したものです。 また、 -> impl Trait 、 let x: impl Trait 、およびabstract typeの推論をトレイトimplsの関連する型に適用することも素晴らしいことです。

具体的には、私が嫌いな関数の引数にこの新しい構文を追加するという概念です。 引き込まれている他の機能と同じことはしません。 これは、すでに持っている構文と同じことを行いますが、エッジケースが多く、適用性が低くなっています。 :/

@nikomatsakis

これがどれほど真実であるかを知るのは本当に難しいです。

それなら保守的であるという側で誤りを犯すべきだと私には思えますか? より多くの時間で設計の信頼性を高めることができますか(自動特性のリークを別の機能ゲートの下に置き、残りのimpl Traitを安定させている間は夜間のみ)? 今リークしなければ、後でいつでも自動トレイトリークのサポートを追加できます。

しかしまた、私はここで遅れることについてかなり緊張しています。 [..]今遅れると、今年のロードマップを実現するのに苦労するのではないかとかなり心配しています。

わかりました! しかし、あなたが考えていると確信しているように、ここでの決定は、今後何年にもわたって私たちと共に生きていきます。

たとえば、 !Sendまたは!Syncタイプが導入されたときに警告するリントを使用できます。 偶発的なsemver違反を防ぐのに役立つsemvercheckerの導入については、長い間話し合ってきました。これは、それが役立つ別のケースのようです。 要するに、問題ですが、私は重大な問題ではないと思います。

これは聞いて良かったです! 🎉そして、これは私の懸念をほとんど和らげると思います。

確かに、 Sendような特定の自動特性を「選び出す」という考えには間違いなく緊張しています。

私はこの感情に非常に同意します👍。

いずれにせよ、あなたが本当に望んでいること、そして私たちが延期しようとしていること=)は、一種の「条件付き」バインドです( Tが送信の場合は送信)。 これはまさに自動特性があなたに与えるものです。

コードがこれを明示的に述べていれば、 T: Send => Foo<T>: Send方がよく理解できると思います。

fn foo<T: Extra, trait Extra = Send>(x: T) -> impl Bar + Extra {..}

WG-Traitsで説明したように、ここではまったく推論できない可能性があるため、 Send以外のものが必要な場合は、常にExtraを指定する必要があります。 。

@rpjohnst

dyn Traitとの類似性は、正直なところ、誤ったものです。

引数の位置にあるimpl Traitに関しては、falseですが、 -> impl Traitはどちらも実存型であるため、そうで

  • これで、関数の実装の詳細(型パラメーターの宣言方法)がインターフェイスの一部になります。これは、型を記述できないためです。

ターボフィッシュのせいで、型パラメーターの順序が実装の詳細になったことがないことに注意してください。この点で、 impl Traitは、ターボフィッシュで特定の型引数を指定しないままにしておくことができるため、役立つと思います。 。

[..]そこにある既存の構文は、traitTraitのような構文糖衣構文の必要性を誰も感じないほど十分に優れています。。

絶対とは絶対言うな? https://github.com/rust-lang/rfcs/issues/2274

@nikomatsakisのように、これらの土壇場でのコメントに注意を払ってくれたことに本当に感謝しています。 貨物列車の前に身を投げ出そうとしているように感じることがあることを私は知っています、特にこれと同じくらい長い間望まれる機能のために!


@daboross 、オプトアウトのアイデアをもう少し掘り下げたいと思いました。 一見すると、署名を完全に述べることができるので、それは有望に思えますが、一般的なケースを簡潔にするデフォルトを使用します。

ただし、残念ながら、全体像を見始めると、いくつかの問題が発生します。

  • 自動特性がimpl Traitオプトアウトとして扱われた場合、それらもdyn Traitオプトアウトである必要があります。
  • もちろん、これは、これらの構成が引数の位置で使用されている場合でも当てはまります。
  • しかし、ジェネリックが異なる動作をするのはかなり奇妙なことです。 つまり、 fn foo<T>(t: T)場合、デフォルトでT: Send合理的に期待できます。
  • もちろん、これにはメカニズムがあり、現在はSizedのみ適用されています。 これは、デフォルトでどこでも想定されている特性であり、 ?Sized書くことでオプトアウトします。

?Sizedメカニズムは、Rustの最もあいまいで、教えるのが難しい側面の1つであり、一般に、他の概念に拡張することを非常に嫌がっています。 Sendように中心的な概念にそれを使用することは危険であるように思われます-もちろん、それが大きな重大な変化になることは言うまでもありません。

けれども、より多くの情報:ジェネリック医薬品の美しさの一部今日はあなたが効果的に型が実装自動トレイトかジェネリック終わって、そしてちょうどその情報を持つことができるということですので、私たちは本当に、ジェネリック医薬品の自動形質前提に焼くにしたくありません「フロースルー」。 たとえば、 fn f<T>(t: T) -> Option<T>について考えてみます。 Sendであるかどうかに関係なく、 Tを渡すことができ、 T場合、出力はSend Tになります。 これは、Rustのジェネリックストーリーの非常に重要な部分です。

dyn Traitも問題があります。 特に、個別にコンパイルするため、この「オプトアウト」の性質を、 SendやSyncなどの「よく知られている」自動特性のみに制限する必要があります。 それはおそらく、外部使用のためにauto traitを決して安定させないことを意味するでしょう。

最後に、「リーク」デザインは、不透明(OPAQUE)型を返すためにnewtypeラッパーを作成したときに今日起こったことを明示的にモデル化したことを繰り返す価値があります。 基本的に、「漏れ」はそもそも自動車の特性に固有の側面であると私は信じています。 トレードオフがありますが、それが機能の核心であり、それに応じて新しい機能が相互作用するように努力する必要があると思います。


@rpjohnst

上記のRFCと@nikomatsakisの要約コメントに関する広範な議論の後、議論の位置の問題について追加することはあまりありません。

これで、関数の実装の詳細(型パラメーターの宣言方法)がインターフェイスの一部になります。これは、型を記述できないためです。

これが何を意味するのかわかりません。 拡大できますか?

また、次のようなフレーズにも注意したいと思います。

fn f(t:impl Trait)は、代わりに「できるという理由だけで」追加されたように感じます

誠実な議論を損なう(繰り返されるパターンであるため、これを呼び出しています)。 RFCは、機能を動機付け、ここで行っている議論のいくつかに反論するためにかなりの長さを費やしています。もちろん、スレッドに関する議論や、RFCの以前の反復などでの議論は言うまでもありません。

トレードオフが存在し、確かに欠点がありますが、それは私たちが議論の「反対側」の似顔絵に合理的な結論を出すのに役立ちません。

詳細なコメントをありがとうございました! ついに安定版でimpl Traitを出荷できることにとても興奮しているので、現在の実装とそれに至るまでの設計上の決定に大きく偏っています。 とは言うものの、私は可能な限り公平に対応し、ゼロから始めているかのように物事を検討するように最善を尽くします。

auto Trait漏れ

auto Traitリークのアイデアは、長い間私を悩ませていました。ある意味では、Rustの設計目標の多くとは正反対に思えるかもしれません。 C ++やMLファミリーなどの祖先と比較すると、Rustは、関数宣言で明示的に明示的に指定する必要があるという点で珍しいものです。 私の意見では、これによりRustのジェネリック関数が読みやすくなり、理解しやすくなります。また、後方互換性のない変更が行われている場合は比較的明確になります。 const fnへのアプローチでもこのパターンを継続し、関数本体からconst推測するのではなく、関数が自分自身をconstとして明示的に指定する必要があります。 明示的なトレイト境界と同様に、これにより、どの関数がどのように使用できるかを簡単に判断でき、ライブラリの作成者は、実装を少し変更してもユーザーを壊さないという確信が得られます。

とは言うものの、私はFuchsiaオペレーティングシステムでの作業を含め、自分のプロジェクトでリターンポジションimpl Trait幅広く使用してきました。ここでは、自動トレイトリークが正しいデフォルトであると思います。 実際には、リークを削除した結果、戻って+ Sendを基本的にすべてのimpl Trait追加する必要があります。これまでに作成した関数を使用します。 負の境界( + !Send必要)は私にとって興味深いアイデアですが、ほとんどすべての同じ関数で+ !Unpinを記述します。 明示性は、ユーザーの決定を通知したり、コードをより理解しやすくしたりする場合に役立ちます。 この場合、どちらもしないと思います。

SendとSyncは、ユーザーがプログラムする「コンテキスト」です。 Sendと!Send両方のタイプを使用するアプリケーションまたはライブラリを作成することは非常にまれです。 (特に、マルチスレッドであるかどうかに関係なく、中央のエグゼキュータで実行される非同期コードを作成する場合)。 スレッドセーフであるかどうかの選択は、アプリケーションを作成するときに最初に行わなければならない選択の1つであり、それ以降、スレッドセーフであると選択することは、すべてのタイプがSendなければならないことを意味します。 ライブラリの場合、ほとんどの場合、 Sendタイプを使用します。これは、通常、ライブラリを使用しないということは、スレッドコンテキスト内で使用すると、ライブラリが使用できなくなる(または専用スレッドを作成する必要がある)ことを意味するためです。 競合のないparking_lot::Mutexは、最新のCPUで使用した場合のRefCellとほぼ同じパフォーマンスになるため、ユーザーを!Send使用に特化したライブラリ機能に向かわせる動機はありません-ケース。 これらの理由から、関数シグネチャレベルでSend !SendタイプとSendであったimpl Traitタイプに誤って!Sendタイプを導入しました。 この選択には読みやすさと明確さのコストが伴うことは事実ですが、人間工学的および使いやすさの利点とのトレードオフには十分な価値があると思います。

引数位置impl Trait

ここで言うことはあまりありませんが、引数の位置impl Traitに到達するたびに、関数シグネチャの読みやすさと全体的な快適さが大幅に向上することがわかりました。 確かに、今日のRustでは不可能な新しい機能は追加されていませんが、複雑な関数シグネチャの生活の質が大幅に向上し、概念的には戻り位置impl Traitとうまくペアになっています。そしてそれはOOPプログラマーの幸せなRustaceansへの移行を容易にします。 現在、境界を提供するためだけに名前付きジェネリック型を導入する必要があることについては、多くの冗長性があります(たとえば、 fn foo<F>(x: F) where F: FnOnce() Fとfn foo(x: impl FnOnce()) )。 この変更により、この問題が解決され、読み取りと書き込みが容易な関数シグネチャが作成され、IMOは-> impl Traitと自然に調和しているように感じられます。

TL; DR:間違いなくトレードオフが伴いますが、私たちの当初の決定は正しいものだったと思います。
Rustが最高の言語であることを確認するために、多くの時間と労力を費やして、皆さんが率直に発言してくれたことに本当に感謝しています。

@Centril

引数の位置にあるimplTraitに関しては、falseですが、-> impl Traitはどちらも実存型であるため、そうではありません。

はい、それは私が意味したことです。

@aturon

...のようなフレーズは誠実な議論を損なう

そうです、お詫びします。 私は自分の主張を他の場所でより良くしたと信じています。

これで、関数の実装の詳細(型パラメーターの宣言方法)がインターフェイスの一部になります。これは、型を記述できないためです。

これが何を意味するのかわかりません。 拡大できますか?

引数の位置でimpl Traitがサポートされているため、この関数は次の2つの方法で記述できます。

fn f(t: impl Trait)
fn f<T: Trait>(t: T)

フォームの選択により、APIコンシューマーが特定のインスタンス化の名前を書き留めることができるかどうかが決まります(たとえば、そのアドレスを取得するため)。 impl Traitバリアントではそれができません。また、 <T>構文を使用するように署名を書き直さないと、これを常に回避できるとは限りません。 さらに、 <T>構文への移行は、重大な変更です。

さらなる似顔絵の危険を冒して、これの動機は、教えること、学ぶこと、そして使うことがより簡単であるということです。 ただし、2つのどちらを選択するかは、型パラメーターの順序と同様に、関数のインターフェイスの主要部分でもあるため、これが適切に対処されているとは感じません。実際には、使いやすさや使いやすさに異論はありません。より快適な関数シグネチャが得られます。

学習可能性/人間工学に動機付けられた他の「単純だが限定的->複雑だが一般的な」変更のいずれにも、まったくこのようにインターフェースを壊す変更が含まれるかどうかはわかりません。 単純な方法の複雑な同等の動作は同じように動作し、インターフェイスまたは動作をすでに変更している場合にのみ切り替える必要があります(たとえば、生涯の省略、人間工学の一致、 -> impl Trait )、または変更は同じように一般的であり、普遍的に適用されます(例:モジュール/パス、帯域内ライフタイム、 dyn Trait )。

具体的には、図書館でこの問題が発生し始めるのではないかと心配しています。「誰もがCopy / Cloneを導出することを忘れないでください」のようになりますが、a)それは重大な変化であり、b)特にそれが機能が設計された目的であるため、戻るための緊張が常にあります!

@cramertj関数シグネチャの冗長性に関する限り、他の方法でそれを取り除くことができますか? バンド内の寿命は、後方参照なしで逃げることができました。 おそらく、「帯域内タイプのパラメーター」と道徳的に同等のことをなんとかして行うことができます。 言い換えれば、「変更は同じように一般的であり、普遍的に適用されることを意図しています」。

@rpjohnst

さらに、 <T>構文への移行は、重大な変更です。

必ずしもそうではありません、とhttps://github.com/rust-lang/rfcs/pull/2176あなたはパラメータの余分なタイプを追加することができますT: Traitあなたがで破損に言及されない限り終了とturbofishにまだ(働くだろうターボフィッシュの破損以外の手段)。

impl Traitバリアントではそれができません。また、 <T>構文を使用するように署名を書き直さないと、これを常に回避できるとは限りません。 さらに、 <T>構文への移行は、重大な変更です。

また、 <T>構文からの移行は重大な変更であると思います(呼び出し元がターボフィッシュを使用してTの値を明示的に指定できなくなったため)。

更新:関数がimpl Traitを使用している場合、現在、ターボフィッシュの使用はまったく許可されていないことに注意してください。通常のジェネリックパラメーターがある場合でも同様です。

@nikomatsakis古い署名に明示的な型パラメーターと暗黙的な型パラメーターが混在している場合、明示的な構文への移行も重大な変更になる可能性があります。 n型パラメーターを提供した人は、 n + 1を提供する必要があります。代わりに@ CentrilのRFCが解決を目的としたケースの1つでした。

更新:関数がimpl Traitを使用している場合、現在、ターボフィッシュの使用はまったく許可されていないことに注意してください。通常のジェネリックパラメーターがある場合でも同様です。

これにより、技術的にはブレークケースの数が減りますが、一方で、特定のインスタンス化に名前を付けることができないケースの数が増えます。 :(

@nikomatsakis

この懸念に真摯に取り組んでいただきありがとうございます。

自動トレイトリークが_正しい_解決策であると言うのはまだ躊躇していますが、事後まで何が最善かを本当に知ることはできないことに同意します。

私は主に先物のユースケースを検討しましたが、それだけではありません。 ローカルタイプからSend / Syncをリークしない限り、さまざまなコンテキストでimpl Traitを使用するのは良い話ではありません。 これを考えると、追加の自動特性を考えると、私の提案は実際には実行可能ではありません。

SyncとSendを選びたくなかったので、それらを想定しました。これは少し恣意的であり、_one_のユースケースにのみ最適だからです。 ただし、すべての自動特性を想定するという選択肢も適切ではありません。 すべてのタイプの+ !Unpin + !...は、実行可能な解決策のようには思えません。

エフェクトシステムや今のところわからないアイデアを思いつくために、さらに5年間の言語デザインがあれば、もっと良いものを思いつくことができるかもしれません。 しかし、今のところ、そしてRustにとっては、100%「自動」の自動特性を持つことが最善の道のようです。

@lfairy

古い署名に明示的な型パラメーターと暗黙的な型パラメーターが混在している場合、明示的な構文への移行も重大な変更になる可能性があります。n個の型パラメーターを提供した人n + 1代わりに

現在、それは許可されていません。 impl Traitを使用する場合、パラメーターに対してターボフィッシュを取得することはありません(前述のとおり)。 ただし、これは長期的な解決策として意図されたものではなく、丸みを帯びた設計を思い付くまでの進め方に関する意見の不一致を回避するためのより保守的な手順です。 (そして、 @ rpjohnstが指摘したように、それ自体に欠点があります。)

私が見たいデザインは、(a) @centrilのRFCなどを受け入れること、および(b)明示的なパラメーターにターボフィッシュを使用できることを示すことです(ただし、 impl Traitタイプは使用できません)。 IMPL形質に明示的なパラメータからの移行を有効に物語があるかもしれない場合は、我々が思っていたので、しかし、我々は、部分的に、それをしませんでした。

@lfairy

これは、 @ CentrilのRFCが解決を目的としたケースの1つでした。

_ [雑学クイズ] _ちなみに、部分的なターボフィッシュが<T: Trait>とimpl Trait間の休憩を緩和できることに気付いたのは、実際にはimpl Trait ;)RFCの目標ではありません

うまくいけば、推論、デフォルト、名前付きパラメーターなどについての信頼が得られれば、部分的なターボフィッシュ、Eventually™も使用できるようになります。

これで最終コメント期間が終了しました。

これが1.26で出荷されている場合、 https://github.com/rust-lang/rust/issues/49373は私にとって非常に重要であるように思われます。 FutureとIteratorが主な用途の2つです。 -ケースとそれらは両方とも、関連するタイプを知ることに大きく依存しています。

課題トラッカーでクイック検索を行いました。#47715はまだ修正が必要なICEです。 安定する前にこれを入手できますか?

今日私がimplTraitで遭遇した何か:
https://play.rust-lang.org/?gist=69bd9ca4d41105f655db5f01ff444496&version=stable

impl Traitはunimplemented!()と互換性がないようです-これは既知の問題ですか?

はい、#36375と#44923を参照してください

RFC 1951の仮定2が、非同期ブロックでのimpl Trait計画された使用のいくつかに反していることに気づきました。 具体的には、一般的なAsRefまたはIntoパラメーターを使用してより人間工学的なAPIを使用し、それを所有する型に変換してからasyncブロックを返す場合でも、 impl Traitタイプは、そのパラメーターの任意のライフタイムにバインドされます。例:

impl HttpClient {
    fn get(&mut self, url: impl Into<Url>) -> impl Future<Output = Response> + '_ {
        let url = url.into();
        async {
            // perform the get
        }
    }
}

fn foo(client: &mut HttpClient) -> impl Future<Output = Response> + '_ {
    let url = Url::parse("http://foo.example.com").unwrap();
    client.get(&url)
}

これを使用すると、 error[E0597]: `url` does not live long enough getに返されるimpl Future一時参照の有効期間が含まれるため、 error[E0597]: `url` does not live long enoughが得られます。 この例は、URLを値でgetに渡すことができるという点で少し工夫されていますが、実際のコードでも同様のケースが発生することはほぼ間違いありません。

私が知る限り、これに対する予想される修正は抽象型であり、具体的には

impl HttpClient {
    abstract type Get<'a>: impl Future<Output = Response> + 'a;
    fn get(&mut self, url: impl Into<Url>) -> Self::Get<'_> {
        let url = url.into();
        async {
            // perform the get
        }
    }
}

間接層を追加することにより、抽象型に必要なジェネリック型と存続期間のパラメーターを明示的に渡す必要があります。

これを書くためのもっと簡潔な方法があるのではないかと思いますか、それともほとんどすべての関数に抽象型が使用され、裸のimpl Trait戻り型になることはありませんか?

@ Nemo157はい、 https://github.com/rust-lang/rust/issues/42940を参照して

だから、私はあの問題にコメントを@cramertj理解していれば、あなたはの定義にエラーになるだろうHttpClient::getのようなもの`get` returns an `impl Future` type which is bounded to live for `'_`, but this type could potentially contain data with a shorter lifetime inside the type of `url` 。 (RFCはimpl Traitが_all_ジェネリック型パラメーターをキャプチャすることを明示的に指定しているため、明示的に宣言されたライフタイムよりも短いライフタイムを含む可能性のある型をキャプチャできるのはバグです)。

このことから、キャプチャされる型パラメータを明示的に宣言できるようにするために、名目上の抽象型を宣言することが唯一の修正であるように見えます。

実際、それは重大な変化のようです。 したがって、その場合のエラーが追加される場合は、すぐに追加することをお勧めします。

編集:そしてコメントを読み直すと、それが言っていることではないと思うので、抽象型を使用せずにこれを回避する潜在的な方法があるかどうかについてはまだ混乱しています。

@ Nemo157はい、#42940を修正すると、 Url存続期間に関係なく、リターンタイプが自己の借用と同じ期間存続するように指定できるため、存続期間の問題が修正されます。 これは間違いなく私たちが行いたい変更ですが、これを行うには下位互換性があると思います。リターンタイプの有効期間を短くすることはできず、リターンタイプの使用方法が制限されすぎています。

たとえば、「パラメータIterは十分に長く存続しない可能性があります」という次のエラー:

fn foo<'a, Iter>(_: &'a mut u32, iter: Iter) -> impl Iterator<Item = u32> + 'a
    where Iter: Iterator<Item = u32>
{
    iter
}

関数のジェネリックにIterを含めるだけでは、それをreturn型に含めるには不十分ですが、現在、関数の呼び出し元はそれを誤って想定しています。 これは間違いなくバグであり、修正する必要がありますが、逆方向に修正できると思います。互換性があり、安定化を妨げることはありません。

#46541が完了したようです。 誰かがOPを更新できますか?

構文abstract type Foo = ...;がtype Foo = impl ...;よりも選択された理由はありますか? 構文の一貫性のために後者の方がはるかに好きで、しばらく前にこれについての議論を思い出しましたが、それを見つけることができないようです。

私はtype Foo = impl ...;またはtype Foo: ...;に偏っていますが、 abstractは不要な奇妙なボールのようです。

私が正しく思い出すと、主な懸念の1つは、人々がtype X = Yテキストの置換のように解釈することを学んだことXを該当する場合はYに置き換えてください」)。 これはtype X = impl Yでは機能しません。

typeはletように機能するという直感があるので、私はtype X = impl Yを好みますが...

@alexregのトピックに関する議論がたくさんありますRFC 2071で。 TL; DR: type Foo = impl Trait;は、 impl Traitを「より明示的な」形式にデシュガーする機能を壊し、少し賢い構文置換として機能するタイプエイリアスに関する人々の直感を壊します。

私はFoo = implと入力するのが好きです...; またはFooと入力します:...;、abstractは不要な奇妙なボールのようです

私のexists type Foo: Trait;キャンプに参加する必要があります:wink:

@cramertjうーん。 私はそのいくつかについて自分自身をリフレッシュしたばかりです、そして私が正直であるならば、私は@withoutboatsの推論を理解しているとは言えません。 それは私にとって最も直感的であるように思われます(反例はありますか?)そして私が得られない脱糖についてのビット。 私の直感は@lnicolaのように機能すると思います。 また、この構文はhttps://github.com/rust-lang/rfcs/pull/2071#issuecomment -319012123のようなことを行うのに最適だと思い

exists type Foo: Trait;は少し改善されていますが、 existsキーワードは削除します。 type Foo: Trait;は文句を言うほど私を悩ませません。 😉 abstract @eddybが言うように、ちょうど余分/の変わり者です。

@alexreg

これは現在の構文でも実行できますか?

はい、しかしそれははるかに厄介です。 これが、 = impl Trait構文( abstractキーワードを法として)を好む主な理由でした。

type Foo = (impl Bar, impl Baz);
type IterDisplay = impl Iterator<Item=impl Display>;

// can be written like this:

exists type Foo1: Bar;
exists type Foo2: Baz;
exists type Foo: (Foo1, Foo2);

exists type IterDisplayItem: Display;
exists type IterDisplay: Iterator<Item=IterDisplayItem>;

編集:上記のexists type Foo: (Foo1, Foo2);はtype Foo = (Foo1, Foo2);ます。 混乱させて申し訳ありません。

@cramertj構文はexistsは適切なキーワードである必要がありますか?

@cramertjそうですね、私はあなたがそのようなことをしなければならないと思っていました... = impl Traitを好む正当な理由だと思います! 正直なところ、(単純な型のエイリアスと比較して)ここでの実存型の置換についての直感が十分に壊れていると人々が考えるなら、なぜ次の妥協をしないのですか?

exists type Foo = (impl Bar, impl Baz);

(正直なところ、すべてに単一のtypeキーワードを使用するという一貫性を持たせたいだけです。)

私は見つけます:

exists type Foo: (Foo1, Foo2);

非常に奇妙です。 使用してFoo: (Foo1, Foo2) RHSがバインドされていないところは、どのようにと一致していないTy: Bound言語の他の場所で使用されています。

次のフォームは私には問題ないようです。

exists type Foo: Bar + Baz;  // <=> "There exists a type Foo which satisfies Bar and Baz."
                             // Reads super well!

type Foo = impl Bar + Baz;

type Bar = (impl Foo, impl Bar);

また、ここではabstractを単語として使用しないことを好みます。

exists type Foo: (Foo1, Foo2);とても奇妙だと思います

それは確かに私には間違いのように見えます、そして私はそれがtype Foo = (Foo1, Foo2);と言うべきだと思います。

ここでabstract typeとexists typeバイクシェッドしている場合は、前者を確実にサポートします。 主な理由は、「抽象」が形容詞として機能するためです。 会話では簡単に「抽象型」と呼べますが、「存在型」と言っても不思議です。

また、 : (Foo, Bar) 、 = Foo + Bar 、 = impl Foo + Barまたは= (impl Foo, impl Barよりも: Foo + Bar方が好きです。 +の使用法は、他のすべての場所でうまく機能します。 =がないということは、完全な型を書き出すことができないことを意味します。 ここでは型エイリアスを作成していません。特定の境界があることが保証されているものの名​​前を作成していますが、明示的に名前を付けることはできません。


https://github.com/rust-lang/rfcs/pull/2071#issuecomment-318852774の構文提案も気に入っています。

type ExistentialFoo: Bar;
type Bar: Baz + Bax;

これは、そのスレッドで述べられているように、少し違いが少なすぎて、あまり明確ではありませんが。

私は(impl Foo, impl Bar)をあなた方の何人かとは非常に異なって解釈しているに違いありません...私にとって、これは型がいくつかの実存型の2タプルであり、 impl Foo + Barとは完全に異なることを意味します。

@alexregそれが@cramertjの意図であった:構文では非常に奇妙だと思います。

exists type Foo: (Foo1, Foo2);

それが何をしているのかについてはまだ非常に不明確なようです-境界は通常、どのような場合でも可能なタイプのタプルを指定せず、 Foo: Foo1 + Foo2構文の意味と簡単に混同される可能性があります。

= (impl Foo, impl Bar)は興味深いアイデアです。それ自体が知られていないタイプで実存的なタプルを作成できるようにすることは興味深いことです。 ただし、これらをサポートする必要はないと思います。これは、impl Fooとimpl Bar 2つの実存型を導入し、次にタプルの3番目の型エイリアスを導入できるためです。

@daborossええと、あなたは「存在するタイプ」ではなく、 「存在するタイプ」を作っています。 これは型理論で呼ばれているものです。 しかし、 「タイプFooが存在する...」という言い回しは、メンタルモデルと型理論の両方の観点からうまく機能

impl Fooとimpl Bar 2つの実存型を導入し、次にタプルの3番目の型エイリアスを導入できるため、これらをサポートする必要はないと思います。

それは人間工学的ではないようです...一時的なものはそれほど素晴らしいイモではありません。

@alexreg注: impl Bar + Baz;が(impl Foo, impl Bar)と同じであると言うつもりはありませんimpl Bar + Baz;が、後者は明らかに2タプルです。

@daboross

それが@cramertjの意図であった

exists type Foo: (Foo1, Foo2);

それが何をしているのかについてはまだ非常に不明確なようです-境界は通常、どのような場合でも可能なタイプのタプルを指定せず、Foo:Foo1 + Foo2構文の意味と簡単に混同される可能性があります。

少し不明確かもしれませんが( (impl Foo, impl Bar)ほど明確ではなく、すぐに直感的に理解できます)、個人的にはFoo1 + Foo2と混同することはないと思います。

=(impl Foo、impl Bar)は興味深いアイデアです-それ自体が知られていないタイプで実存的なタプルを作成できるようにすることは興味深いでしょう。 ただし、これらをサポートする必要はないと思います。これは、implFooとimplBarの2つの実存型を導入し、次にタプルの3番目の型エイリアスを導入できるためです。

ええ、それは初期の提案でした、そして私はまだそれがとても好きです。 これは、現在の構文を使用してとにかく実行できることに注意してください。ただし、3行のコードが必要であり、人間工学的ではありません。 また、 ... = (impl Foo, impl Bar)ような構文がユーザーにとって最も明確であると主張していますが、ここで競合があることはわかっています。

@Centril最初はそうは思いませんでしたが、少し曖昧でしたが、

おっと、 https: //github.com/rust-lang/rust/issues/34511#issuecomment-386763340への私の編集を参照してexists type Foo: (Foo1, Foo2);はtype Foo = (Foo1, Foo2);ます。

@cramertjああ、それは今より理にかなっています。 とにかく、次のことができることが最も人間工学的だと思いませんか? 他のスレッドを閲覧していても、私はそれに対する良い議論を見ていません。

type A = impl Foo;
type B = (impl Foo, impl Bar, String);

@alexregはい、私はそれが最も人間工学的な構文であると思います。

RFC https://github.com/rust-lang/rfcs/pull/2289を使用して、これは@cramertjのスニペットを書き直す方法です。

type Foo = (impl Bar, impl Baz);
type IterDisplay = impl Iterator<Item: Display>;

// alternatively:

exists type IterDisplay: Iterator<Item: Display>;

type IterDisplay: Iterator<Item: Display>;

ただし、型エイリアスの場合、 existsを導入しないと、言語の構文を不必要に複雑にすることなく、表現力を維持するのに役立つと思います。 したがって、複雑さの予算のPOVから、 impl Iteratorはexistsよりも優れているように見えます。 ただし、最後の選択肢は実際には新しい構文を導入しておらず、明確でありながら最短でもあります。

要約すると、次の両方の形式を許可する必要があると思います( impl Traitと、すでに持っている関連する型の構文の境界の両方で機能するため)。

type Foo = (impl Bar, impl Baz);
type IterDisplay: Iterator<Item: Display>;

編集:どの構文を使用する必要がありますか? IMO、clippyは、最も人間工学的で直接的であるため、使用可能な場合はType: Bound構文を明確に優先する必要があります。

私はtype Foo = impl Traitバリアントよりもtype Foo: Traitバリアントの方がはるかに好きです。 これは、関連する型構文と一致します。これは、それを含むモジュールの「出力型」でもあるため、適切です。

impl Trait構文は、入力タイプと出力タイプの両方ですでに使用されています。つまり、ポリモーフィックモジュールの印象を与えるリスクがあります。 :(

impl Traitが出力型にのみ使用された場合、関連する型の構文が特性(MLシグネチャに大まかに対応する)に適しているという理由でtype Foo = impl Traitバリアントを好むかもしれませんが、 type Foo = ..構文は、具象モジュール用です。

@rpjohnst

私はtype Foo = impl Traitバリアントよりもtype Foo: Traitバリアントの方がはるかに好きです。

私は同意します、それは可能な限り使用されるべきです。 しかし、バインドされた構文を直接使用できない(impl T, impl U)場合はどうでしょうか。 一時的な型エイリアスを導入すると、読みやすさが損なわれるように思われます。

裸のtype Name: Bound使用すると、 implブロック内で使用すると混乱するようです。

impl Iterator for Foo {
    type Item: Display;

    fn next(&mut self) -> Option<Self::Item> { Some(5) }
}

その構文とキーワードプレフィックスの現在の(?)プランの両方で、 implブロックで使用される一時的なタイプエイリアスを導入するコストもはるかに大きく、これらのタイプエイリアスをモジュールレベルでエクスポートする必要があります(意味的に意味のある名前が付けられています...)これは、プライベートモジュール内の特性実装を定義する比較的一般的なパターン(少なくとも私にとっては)をブロックします。

pub abstract type First: Display;
pub abstract type Second: Debug;

impl Iterator for Foo {
    type Item = (First, Second);

    fn next(&mut self) -> Option<Self::Item> { Some((5, 6)) }
}

vs

impl Iterator for Foo {
    type Item = (impl Display, impl Debug);

    fn next(&mut self) -> Option<Self::Item> { Some((5, 6)) }
}

@ Nemo157両方を許可しないのはなぜですか?

pub type First: Display;
pub type Second: Debug;

impl Iterator for Foo {
    type Item = (First, Second);
    fn next(&mut self) -> Option<Self::Item> { Some((5, 6)) }
}

と:

impl Iterator for Foo {
    type Item = (impl Display, impl Debug);
    fn next(&mut self) -> Option<Self::Item> { Some((5, 6)) }
}

?

同じ機能に2つの構文が必要な理由がわかりません。 type Name = impl Bound;構文だけを使用して、2つの部分の名前を明示的に指定することも可能です。

pub type First = impl Display;
pub type Second = impl Debug;

impl Iterator for Foo {
    type Item = (First, Second);
    fn next(&mut self) -> Option<Self::Item> { Some((5, 6)) }
}

@ Nemo157私は、2つの異なる構文である必要はない(そしてそうすべきではない)type (プレフィックスキーワードなし)はまったく混乱しませんが、言わなければなりません。

@rpjohnst多形モジュールとは一体何ですか? :-)とにかく、型に特性の境界を設定している関連する型定義の後に構文をモデル化する必要がある理由がわかりません。 これは境界とは何の関係もありません。

@alexregポリモーフィックモジュールは、 fn foo(x: impl Trait)と同じように、型パラメーターを持つモジュールです。 それは存在するものではないので、人々にそれが存在すると思われたくないのです。

abstract type (編集:キーワードの使用を提案するのではなく、機能に名前を付ける)は、境界と関係があります。 タイプについて知っているのは境界だけです。 それらと関連するタイプの唯一の違いは、通常は名前を付けられないため、推測されることです。

@ Nemo157 Foo: Bar構文は、他のコンテキスト(関連する型、および型パラメーターの境界)ですでによく知られており、一時的なものを導入せずに使用できる場合は、より人間工学的で(IMO)明確です。

書き込み:

type IterDisplay: Iterator<Item: Display>;

はるかに直接的なwrtのようです。 私が言いたいことと比較して

type IterDisplay = impl Iterator<Item = impl Display>;

これは、私たちがすでに持っている構文を一貫して適用しているだけだと思います。 ですから、それは本当に新しいものではありません。

EDIT2:最初の構文は、rustdocでレンダリングしたい方法でもあります。

関連するタイプに何かを必要とする特性から、implに移行することも非常に簡単になります。

trait Foo {
    type Bar: Baz;
    // stuff...
}

struct Quux;

impl Foo for Quux {
    type Bar: Baz; // Oh look! Same as in the trait; I had to do nothing!
    // stuff...
}

impl Bar構文は、一時的なものを導入する必要がある場合に適しているように見えますが、構文全体に一貫して適用されています。

両方の構文を使用できることimpl Trait 、引数の位置でT: Traitを使用できることとそれほど違いはありません。

編集1:実際、構文が1つしかない場合は特殊な大文字小文字になり、その逆ではありません。

@rpjohnst境界とは明確に関係がないと言ったはずですが、私は違うように頼みます。

とにかく、私はtype Foo: Bar;構文に反対していませんが、念のため、 abstractキーワードを削除しましょう。 type自体は、どのような状況でも非常に明確です。

個人的には、 =とimplは、推論が行われていることを視覚的に示す良いヒントだと思います。 また、大きなファイルをスキミングするときに、それらの場所を簡単に見つけることができます。

また、 type Iter: Iterator<Item = Foo>表示されていると仮定すると、何が起こっているのかを知る前に、 Fooを見つけて、それがタイプか特性かを判断する必要があります。

そして最後に、推論ポイントの視覚的な手がかりは、推論エラーのデバッグと推論エラーメッセージの解釈にも役立つと思います。

したがって、 = / implバリアントは、もう少しペーパーカットを解決すると思います。

@phaylon

また、タイプIter: Iterator<Item = Foo>表示されていると仮定すると、何が起こっているのかを知る前に、 Fooを見つけて、それがタイプか特性かを判断する必要があります。

これはわかりません。 Item = Foo dyn Fooが安定している(そして裸の特性が段階的に廃止されている...)ことを考えると、 Item = Fooは常にタイプである必要がありますか?

@Centril

これはわかりません。 Item = Fooは、dyn Fooが安定している(そして、裸の特性が段階的に廃止されている...)ことを考えると、常にタイプである必要がありますか?

ええ、しかし提案されたimpl少ないバリアントでは、それはバインドされた推論型、または具象型である可能性があります。 例: Iterator<Item = String> vs Iterator<Item = Display> 。 推論が起こっているかどうかを知るには、特性を知る必要があります。

編集:ああ、 :使用していることに気づかなかった。 ちょっと見逃しやすいという意味です:)しかし、あなたはそれらが異なっているということは正しいです。

編集2:この問題は関連する型の外でも当てはまると思います。 type Foo: (Bar, Baz)を考えると、推論がどこで発生するかを知るには、BarとBazを知る必要があります。

@Centril

編集1:実際、構文が1つしかない場合は特殊な大文字小文字になり、その逆ではありません。

現在、_existential_型を宣言する方法は-> impl Trait 1つだけです。 _universal_型を宣言する方法は2つあります(引数リストのT: Traitと: impl Trait )。

ユニバーサルタイプを取り入れたポリモーフィックモジュールがある場合、その周りにいくつかの議論が見られますが、モジュールとトレイト定義の両方での現在のtype Name = Type;使用は、出力タイプパラメーターとしてのものであり、存在する必要がありますタイプ。


@phaylon

ええ、しかし提案されたimpl少ないバリアントでは、それはバインドされた推論型、または具象型である可能性があります。 例: Iterator<Item = String> vs Iterator<Item = Display> 。 推論が起こっているかどうかを知るには、特性を知る必要があります。

impl少ないバリアントは、すべての場合に実存型に: Boundしていると思います。したがって、トレイト境界としてIterator<Item = String>またはIterator<Item: Display>を使用できますが、 Iterator<Item = Display>は無効な宣言になります。

@ Nemo157
あなたは関連する活字ケースに関して正しいです、私の悪いところです。 しかし(私の編集で述べたように) type Foo: (A, B)まだ問題があると思います。 AまたはBいずれかがタイプまたは特性である可能性があるため、

これも=を使う良い理由だと思います。 :は、いくつかのことが推測されていることを示しているだけで、どれを示しているのかはわかりません。 type Foo = (A, impl B)は私にはもっと明確に思えます。

また、 implすると、特性とは何か、提供する必要がないものについての追加のコンテキストとして、コードスニペットを読んで提供する方が簡単だと思います。

編集:いくつかのクレジット:私の議論は基本的にここの@alexregと同じです、私はimplが好ましいと思う理由を拡張したかっただけです。

現在、存在型を宣言する方法は-> impl Trait 1つだけです。 ユニバーサル型を宣言する方法は2つあります(引数リストのT: Traitと: impl Trait )。

それが私が言っていることです:PIなぜ全称記号には2つの方法があるのに、他の場所には1つしか存在しない( dyn Trait無視する)必要があるのですか?

ユーザーが言語のさまざまな部分を学習してtype Foo: Bound;とtype Foo = impl Bound;を書きに行くことも同様にありそうですが、すべての場合で1つの構文が明らかに優れているとは言えません。 ある構文がいくつかの事柄に、別の構文が別の事柄に適していることは私には明らかです。

@phaylon

これも=と一緒に行く良い理由だと思います。 :は、いくつかのことが推測されていることを示すだけで、どれを示しているのかはわかりません。 タイプFoo =(A、impl B)は私にはより明確に思えます。

はい、これはおそらく別の正当な理由です。 定義から定義へとジャンプして、存在記号で何が定量化されているかを理解するには、実際に解凍する必要があります。

もう1つのことは、その構文の下で、関連付けられた型バインディング内で:許可することさえできるでしょうか。 この提案された構文では他の方法で実存型を構成/結合できないことを考えると、私には少し奇妙な特殊なケースのように思えます。 その構文を使用する最も一貫したアプローチは、次のようになると思います。

type A: Foo;
type B: Bar;
type C: Baz;
type D: Iterator<Item = C>; 
type E = (A, Vec<B>, D);

私(およびここにいる他の人)が好む構文を使用すると、これをすべて1行で記述でき、さらに、定量化がどこで行われているのかがすぐにわかります。

type E = (impl Foo, Vec<impl Bar>, impl Iterator<Item = impl Baz>);

上記とは関係ありません:毎晩let x: impl Traitを実装するためにいつプレイしますか? 私はしばらくの間この機能を見逃してきました。

@alexreg

もう1つのことは、その構文の下で、関連付けられた型バインディング内で:許可することさえできるでしょうか。

はい、どうしてですか。 これは、rust-lang / rfcs#2289 + type Foo: Bound自然な効果です。

次のこともできます。

type E = (impl Foo, Vec<impl Bar>, impl Iterator<Item: Baz>);

@ Centril2つの代替構文を許可するのは悪い考えだと思います。 「決められなかったので、両方をサポートする」症候群のようなにおいがします。 それらを組み合わせて一致させるコードを見るのは本当に目障りです!

@Centril私はあなたのRFCについて@nikomatsakisとimpl Iterator<Item = impl Baz>と書きたいです。 素晴らしく、明白です。

@alexregこれは公平です。

しかし、(残念ながら)残念ながら(POVによって異なりますが)、引数の位置にimpl Traitを指定して、 Foo: Barとimpl Bar両方を持つ「2つの代替構文を許可する」をすでに開始しています。

全称記号用ですが、 impl Trait表記は、二重性のどちら側にあるかを実際には気にしません。 結局のところ、 any Traitとsome Traitは使用しません

すでに「決められなかった」「二元性の側面は構文的に重要ではない」という選択をしたことを考えると、ユーザーが得られないように「決められない」をどこにでも適用することは一貫しているようです。 「でも、あそこにこう書けたら、ここに書いてみませんか?」 ;)


PS:

再。 impl Iterator<Item = impl Baz> where句の境界としては機能しません。 したがって、 Iter: Iterator<Item = impl Baz>ように混合する必要があります。 均一に機能するには、 Iter = impl Iterator<Item = impl Baz>を許可する必要があります(多分そうすべきですか?)。

使用して: Bound代わりのです= impl Bound 、また、明示的であるだけ短く^、 -
X = TyとX: Ty間隔の違いにより、構文が読みやすくなると思います。

私自身のアドバイスを無視して、RFCでこの会話を続けましょう;)

しかし、(残念ながら)残念ながら(POVによって異なりますが)、引数の位置にimpl Traitを使用して「2つの代替構文を許可する」ことをすでに開始しているため、Foo:BarとimplBarの両方が同じことを意味するように機能します。

私たちはそうしましたが、対称性/一貫性の観点から選択が行われたと思います。 一般的に型付けされた引数は、普遍的に型付けされた( impl Trait )引数よりも厳密に強力です。 しかし、リターン位置にimpl Traitを導入していたので、引数位置に導入するのは理にかなっています。

すでに「決められなかった」「二元性の側面は構文的に重要ではない」という選択をしたことを考えると、ユーザーが得られないように「決められない」をどこにでも適用することは一貫しているようです。 「でも、あそこにこう書けたら、ここに書いてみませんか?」 ;)

腕を上げて「すべてを実装しよう」と言うべき時点かどうかはわかりません。 ここでは、利益に関して明確な議論はありません。

PS:

再。 impl Iterator<Item = impl Baz> where句の境界としては機能しません。 したがって、 Iter: Iterator<Item = impl Baz>ように混合する必要があります。 均一に機能するには、 Iter = impl Iterator<Item = impl Baz>を許可する必要があります(多分そうすべきですか?)。

where Iter: Iterator<Item = T>, T: Baz (今のように)をサポートするか、(あなたが提案したように) Iter = impl Iterator<Item = impl Baz>完全にサポートするかのどちらかだと思います。 中途半端な家だけを許可することは、ちょっとした取り締まりのようです。

= impl代わりに: Boundを使用することも明示的で、短い^、-
X = TyとX: Ty間隔の違いにより、構文が読みやすくなると思います。

読みやすいですが、実存的なタイプが使用されていることは、それほど明確/明示的ではないと思います。 この構文の制限により、定義を複数の行に分割する必要がある場合、これはさらに悪化します。

私自身のアドバイスを無視して、RFCでこの会話を続けましょう;)

待って、あなたはあなたのRFCを意味しますか? 私の知る限り、それはそれとこれの両方に関連していると思います。 :-)

待って、あなたはあなたのRFCを意味しますか? 私の知る限り、それはそれとこれの両方に関連していると思います。 :-)

わかった; それではここで続けましょう。

私たちはそうしましたが、対称性/一貫性の観点から選択が行われたと思います。 一般的に型付けされた引数は、普遍的に型付けされた( impl Trait )引数よりも厳密に強力です。 しかし、リターン位置にimpl Traitを導入していたので、引数位置に導入するのは理にかなっています。

私の要点は、一貫性と対称性についてです。 = P
存在記号と全称記号の両方にimpl Traitを書くことが許可されている場合、私には、全称記号と全称記号の両方にType: Traitを使用することも許可されるべきであることは理にかなっています。

表現力に関しては、あなたが言うように前者は後者よりも強力ですが、必ずしもそうである必要はありません。 私たちが彼らをAFAIKにしたければ、彼らは同じように強力かもしれません(私は絶対にそうすべきだと言っているわけではありません..)。

fn foo(bar: impl Trait, baz: typeof bar) { // eww... but possible!
    ...
}

腕を上げて「すべてを実装しよう」と言うべき時点かどうかはわかりません。 ここでは、利益に関して明確な議論はありません。

私の主張は、 「この構文は他の場所でも使用でき、その意味はここでは明確ですが、この場所で書くことはできません」という驚くべきユーザーは、2つの方法(どちらもとにかく慣れている必要があります)よりもコストがかかるということです。 )。 https://github.com/rust-lang/rfcs/pull/2300 (マージ済み)、 https ://github.com/rust-lang/rfcs/pull/2302 (PFCP)、 httpsでも同様のことを行いました。 ://github.com/rust-lang/rfcs/pull/2175 (マージ)以前は別の方法で書き込むことができたとしても、一貫性のある穴を埋めます。

読みやすいですが、実存的なタイプが使用されていることは、それほど明確/明示的ではないと思います。

私の見解では、判読可能で十分です。 Rustが「何よりも明示的」であるとは思わず、過度に冗長である(使用しすぎると構文が多すぎると思います)ことも、使用を思いとどまらせることでコストがかかります。
(何かを頻繁に使用したい場合は、簡潔な構文を使用してください... .unwrap()に対する賄賂として?を.unwrap() )。

この構文の制限により、定義を複数の行に分割する必要がある場合、これはさらに悪化します。

これはわかりません。 Assoc = impl Traitは、前者の方が長いという理由だけで、 Assoc: Traitよりもさらに多くの行分割を引き起こすはずだと私には思えます。

where Iter: Iterator<Item = T>, T: Baz (今のように)をサポートするか、(あなたが提案したように) Iter = impl Iterator<Item = impl Baz>完全にサポートするかのどちらかだと思います。
中途半端な家だけを許可することは、ちょっとした取り締まりのようです。

まさに! 、中途半端な家/警官に行かず、 where Iter: Iterator<Item: Baz>を実装しましょう;)

@Centrilさて、あなたは主に対称性/一貫性の議論で私に勝ちました。 😉両方のフォームを持つことの人間工学も役立ちます。 ただし、やがて、この機能には重いリンティングが必須になります。

明日私の完全な返信でこれを編集します。

編集

@Centrilが指摘しているように、 : Trait (バインドされた)構文を使用したユニバーサル型はすでにサポートされています。 例えば

fn foo<T: Trait>(x: T) { ... }

「適切な」または「洗練された」ユニバーサルタイプと一緒に、例えば

fn foo(x: impl Trait) { ... }

もちろん、前者は後者よりも強力ですが、必要なのがすべてである場合、後者はより明確になります(そしておそらくより読みやすくなります)。 実際、可能であれば、後者の形式を優先するコンパイラー・リントを用意する必要があると強く信じています。

これで、関数の戻り位置にもimpl Traitが既にあります。これは、実存型を表します。 関連するタイプの特性は形式上存在し、すでに: Trait構文を使用しています。

これは、私が現在Rustに普遍的なタイプの適切な形式とバインドされた形式の両方が存在すること、および同様に実存的なタイプの適切な形式とバインドされた形式が存在することを考えると(後者は現在の特性内でのみ)、私たちは強くすべきだと信じています存在型の適切な形式とバインドされた形式の両方のサポートを特性の外側に拡張します。 つまり、一般的に、および関連するタイプの両方で、以下をサポートする必要があり

type A: Iterator<Item: Foo + Bar>;
type B = (impl Baz, impl Debug, String);

2番目に、このコメントで提案されているコンパイラのリンティング動作も示しています。これにより、実際の一般的な存在型の表現のばらつきが大幅に減少するはずです。

1つのキーワードの下で普遍的で存在する定量化を混同することは間違いであり、一貫性の議論は私にはうまくいかないと今でも信じています。 単一のキーワードが関数シグネチャで機能する唯一の理由は、コンテキストによって、各位置で1つの形式の数量化のみを使用するように必然的に制約されるためです。 あなたが同じ制約を持っていないものであると私が見ることができた潜在的な砂糖があります

struct Foo {
    pub foo: impl Display,
}

これは、実存的または全称記号の省略形ですか? 関数シグネチャでimpl Traitを使用することから得られた直感から、どのように決定できるかわかりません。 実際に両方として使用しようとすると、この位置での匿名全称記号は役に立たないことがすぐにわかります。したがって、存在記号である必要がありますが、関数の引数のimpl Traitと矛盾するapperasです。

これらは2つの根本的に異なる操作です。どちらも特性境界を使用しますが、実存型を宣言する2つの方法があると、新規参入者の混乱が減る理由はわかりません。 type Name: Traitを使おうとすることが実際に初心者が行う可能性が高い場合、これはlintを介して解決できます。

    type Foo: Display;
    ^^^^^^^^^^^^^^^^^^
note: were you attempting to create an existential type?
note: suggested replacement `type Foo = impl Display`

そして、私はあなたの議論の別の定式化を思いついたので、私ははるかに受け入れやすくなります。実際のコンピューターに到達するまで、RFCを読み直して投稿する必要があります。

私はまだRFCについてコメントするのに十分なRustの経験がないように感じます。 ただし、 Drops of Diamondシャーディング実装の一部としてEthereumのシャーディングプロトコルを構築するためにRustlibp2pと一緒に使用するために、この機能が夜間の安定したRustにマージされるのを見ることに興味があります。 私はこの問題を購読しましたが、すべてのコメントに追いつく時間がありません! コメントをざっと読むことなく、この問題の最新情報を入手するにはどうすればよいですか?

私はまだRFCについてコメントするのに十分なRustの経験がないように感じます。 ただし、 Drops of Diamondシャーディング実装の一部としてEthereumのシャーディングプロトコルを構築するためにRustlibp2pと一緒に使用するために、この機能が夜間の安定したRustにマージされるのを見ることに興味があります。 私はこの問題を購読しましたが、すべてのコメントに追いつく時間がありません! コメントをざっと読むことなく、この問題の最新情報を入手するにはどうすればよいですか? 現時点では、問題をサブスクライブせずに、時々チェックインすることでそれを実行する必要があるようです。 これに関するハイレベルなニュースを入手するために、私が電子メールで購読できればいいのですが。

1つのキーワードの下で普遍的および存在記号を混同することは間違いであり、一貫性の議論は私には機能しないと私は今でも信じています。

一般的な原則として、この機能とは関係なく、この一連の推論には問題があると思います。

私たちは、言語が別の歴史の展開の下にあることを望んでいた方法ではなくimpl Traitは安定しているため、それを望まない場合があります。 X、Y、Zが間違いだと思っていても(個人的にはRustのデザインの間違いだと思うものはたくさん見つかりましたが、受け入れて想定しています...)、私たちは今、彼らと一緒に暮らす必要があります。新しい機能を前提として、すべてをどのように組み合わせることができるかについて(一貫性を保つ)。

議論の中で、RFCのコーパス全体とそのままの言語は、公理ではなく、強力な議論であるかのように受け取られるべきだと思います。


あなたはそれを主張することができます(しかし私はそうしません):

struct Foo {
    pub foo: impl Display,
}

意味的には次のものと同等です。

struct Foo<T: Display> {
    pub foo: T,
}

関数の引数の推論の下で。

基本的に、 impl Traitを考えると、 「これはreturn-typeのようなものですか、それとも引数のようなものですか?」と考える必要があり


type Name: Traitを使おうとすることが実際に初心者が行う可能性が高い場合、これはlintを介して解決できます。

私も糸くずを出しますが、反対方向です。 次の方法は慣用的である必要があると思います。

// GOOD:
type Foo: Iterator<Item: Display>;

type Bar = (impl Display, impl Debug);

// BAD
type Foo = impl Iterator<Item = impl Display>;

type Bar0: Display;
type Bar1: Debug;
type Bar = (Bar0, Bar1);

わかりました。RFC2071が示唆していると私が信じている代替の定式化は、この問題で議論されている可能性がありますが、明示的には述べられていません。

存在記号型を宣言する方法は_1つ_しかありません: existential type Name: Bound; (RFCで指定されているため、 existentialを使用します。この定式化でキーワードを削除することに完全に反対しているわけではありません)。

さらに、現在のスコープで名前のない存在記号型を暗黙的に宣言するための砂糖があります: impl Bound (当面は関数の引数の全称記号を無視します)。

したがって、現在のリターンタイプの使用法は、単純な脱糖です。

fn foo() -> impl Iterator<Item = impl Display> { ... }
existential type _0: Display;
existential type _1: Iterator<Item = _0>;
fn foo() -> _1 { ... }

const 、 static 、 let拡張することも同様に簡単です。

RFCに記載されていない拡張機能の1つは、この砂糖をtype Alias = Concrete;構文でサポートすることです。

type Foo = impl Iterator<Item = impl Display>;

これは実際には砂糖です

existential type _0: Display;
existential type _1: Iterator<Item = _0>;
type Foo = _1;

次に、型エイリアスの透過的な性質に依存して、現在のモジュールがFooを調べ、それが存在する型を参照していることを確認できるようにします。

実際、可能であれば、後者の形式を優先するコンパイラー・リントを用意する必要があると強く信じています。

私は@alexregのコメントとほぼ一致していimpl Traitが機能しないため、ライブラリの変更を壊すsemverを奨励するリスクがあるため、 arg: impl Traitに向けてリントすることについて懸念があります。ターボフィッシュを使用します(現在、うまく機能させるには部分的なターボフィッシュが必要です)。 したがって、クリップのリンティングは、タイプエイリアスの場合(問題を引き起こすターボフィッシュがない場合)よりも簡単ではありません。

私は@alexregのコメントとほぼ一致していリントすることについて、いくつかの懸念があります。これは主に、impl Traitがturbofishで機能しないため、ライブラリでsemverを壊す変更を奨励するリスクがあるためです(現在、うまく機能させるには、部分的なターボフィッシュが必要です)。 したがって、クリップのリンティングは、タイプエイリアスの場合(問題を引き起こすターボフィッシュがない場合)よりも簡単ではありません。

@CentrilはこれをIRCで取り上げたばかりであり、下位互換性(壊れやすい)に関しては公正な点であることに同意します。 部分的なターボフィッシュが着陸した場合、コンパイラーのリントを追加する必要があると思いますが、それまでは追加しないでください。

だから...私たちは今、名前付き存在型の構文についてかなり多くの議論をしました。 結論を出してRFC / PRの投稿に書き込んで、誰かが実際の実装に取り​​掛かることができるようにしませんか? :-)

個人的には、存在に名前を付けたら、どこでもimpl Trait使用しないように、リント(ある場合)を使用したいと思います。

@rpjohnst名前付きの存在に関して、私と@Centrilに確かに同意します...関数の引数でそれらから離れることに関しては、それは可能性ですが、おそらく別の場所での議論です。 この文脈で単純さを好むか一般性を好むかによって異なります。

引数の位置にあるimpl TraitのRFCは最新ですか? もしそうなら、そのセマンティクスは_universal_であると言っても安全ですか? もしそうなら:私は泣きたいです。 深く。

@phaazon : impl Trait Rust 1.26リリースノート:

そこにあるタイプ理論家への補足:これは実存的ではなく、依然として普遍的です。 言い換えると、impl Traitは入力位置では普遍的ですが、出力位置では存在します。

それについての私の考えを表現するためだけに:

  • 型変数の構文はすでにありますが、実際には、任意の型変数の使用法がいくつかあります(つまり、型変数を1つの場所にドロップするのではなく、複数の場所で使用することがよくあります)。
  • 共変の存在は、ランクn関数への扉を開きます。これは、特性なしでは今のところ実行するのが困難であり(
  • impl Traitは、「呼び出し先が選択したタイプ」と簡単に参照できます。これは、現在のところ、これを可能にする唯一の言語構造だからです。 呼び出し元によるタイプの選択は、いくつかの構成を介してすでに利用可能です。

議論の立場にあるimpl Trait現在の決定は残念だと思います。 :泣く:

私は本当に、議論の位置の現在の決定における暗黙の特性は残念だと思います。 😢

私はこれに少し悩まされていますが、今はlet x: impl Trait実装に時間を費やしたほうがいいと思います。

共変の存在は私たちにランクn関数への扉を開くでしょう

すでに構文( fn foo(f: impl for<T: Trait> Fn(T)) )(別名「タイプHRTB」)がありますが、まだ実装されていません。 fn foo(f: impl Fn(impl Trait))は、「ネストされたimpl Traitは許可されていません」というエラーを生成します。タイプHRTBを取得するときに、上位バージョンを意味するようにしたいと思います。

これは、 Fn(&'_ T) for<'a> Fn(&'a T)意味するのと似ているので、物議を醸すとは思わない。

現在のドラフトを見ると、引数の位置にあるimpl Traitは_universal_ですが、 impl for<_> Traitはそれを_existential_に変えると言っていますか?! それはどれほどクレイジーですか?

_ユニバーサル_を構築するために_さらに別の方法_を導入する必要があると考えたのはなぜですか? つまり:

fn foo(x: impl MyTrait)

匿名型変数は型に1回しか表示されないため、興味深い

fn foo(x: impl Trait) -> impl Trait

明らかに動作しません。 私たちは人々に、単にそれを学び、それをどこでも使用するのではなく、より一般的なイディオムからより制限付加価値のない機能を追加する-RFCで読んだ学習の仮定は、新参者の代わりに私たちが考える奇妙な議論です-彼らはまだ物事を学ぶ必要があるので、なぜ構文をかなり曖昧にするのですか?ここで学習曲線を下げるためにみんな? 人々が普遍的対実存的(そして1つになると、原則は正しい言葉で非常に単純です)に慣れるとき、人々は私たちが両方とwhereを表現するために同じキーワード/パターンを持っている理由を考え始めますおよびインプレーステンプレートパラメータ。

ああ、私はこれのすべてがすでに受け入れられたと思います、そして私は何のために怒鳴っています。 本当に残念だと思います。 RFCの決定に失望したのは私だけではないと確信しています。

(機能が安定した後にこれを議論、それに続けて多くのポイントはおそらくありませんここを参照してください私は同意する説得力の引数(のための)理由のためにimpl Trait引数の位置にあるが、それがないのセマンティクスを持つことが賢明であるがと。コヒーレントTlの、DRが、それはなぜ同じ理由だfn foo(arg: Box<Trait>)とほぼ同じように動作しますfn foo<T: Trait>(arg: Box<T>)にもかかわらず、 dyn Trait実存であり、今で置き換えるdyn impl 。)

現在のドラフトを見ると、引数の位置にあるimpl Traitは普遍的ですが、 impl for<_> Traitはそれを実存主義に変えると言っていますか?!

いいえ、どちらも普遍的です。 上位の用途は次のようになります。

fn foo<F: for<G: Fn(X) -> Y> Fn(G) -> Z>(f: F) {...}

これは、追加されると同時に(つまり、 impl Trait変更を加えずに)次のように記述できます。

fn foo(f: impl for<G: Fn(X) -> Y> Fn(G) -> Z) {...}

これは普遍的なimpl Traitであり、 TraitがHRTB( impl for<'a> Fn(&'a T)と同様)であるというだけです。
私たちは(私はそうです期待している)ことを決定した場合はimpl Traitの内部でFn(...)引数も普遍的であり、あなたは同じ効果を達成するために、これを書くことができます:

fn foo(f: impl Fn(impl Fn(X) -> Y) -> Z) {...}

これが「上位」の意味だと思いましたが、そうでない場合はお知らせください。

さらに興味深いの決定、すなわち、この(「他のクロージャを取るいくつかのクロージャを返す」という意味と思われる)ことができ、実存の位置に同じ処理を適用することが考えられます。

fn foo() -> impl for<G: Fn(X) -> Y> Fn(G) -> Z {...}

このように書かれる:

fn foo() -> impl Fn(impl Fn(X) -> Y) -> Z {...}

これは、ユニバーサルimpl Traitを含む実存的なimpl Trait impl Trait (囲み関数ではなく、実存的な

@eddyb一貫性を保ち、初心者を混乱させないために、一般に実存的および全称
存在記号のキーワードは、存在型にも再利用できるのではないでしょうか。
実存的(および普遍的)数量化にimplを使用しているのに、実存的型にはexistentialを使用しているのはなぜですか?

私は3つのポイントを述べたいと思います:

  • impl Traitが実存的であるか普遍的であるかを議論するメリットはあまりありません。 そこにいるプログラマーのほとんどは、おそらく型理論のハンドブックを十分に読んでいませんでした。 問題は、人々がそれを気に入っているのか、それとも混乱しているのかということです。 その質問に答えるために、このスレッド、 reddit、またはフォーラムの両方で、何らかの形のフィードバックを見ることができます。 さらに説明が必要な場合は、直感的または驚くべき機能のないテストに失敗します。 したがって、他の機能よりも多くの質問があるかどうか、そして彼らがどれほど多くの人々とどれほど混乱しているかを調べる必要があります。 このフィードバックが安定化後に届くのは確かに悲しいことであり、この現象について何かを行う必要がありますが、それは別の議論のためです。
  • 技術的には、さえ安定した後、(必要がある場合はさておき決定をさせる)、この場合の機能を取り除く方法があるでしょう。 それを使用する関数を作成することを阻止し、次のエディションでその機能を削除することは可能です(異なるエディションのクレートからのものである場合はそれらを呼び出す機能を保持します)。 それは錆の安定性の保証を満たします。
  • いいえ、実存型とユニバーサル型を指定するためにさらに2つのキーワードを追加しても、混乱は改善されません。事態はさらに悪化するだけです。

このフィードバックが安定化後に到着するのは本当に悲しいことであり、この現象について何かをする必要があります

それがアイデアである限り、引数の位置でimpl Traitに反対がありました。 このようなフィードバックは_新しいものではありません_、関連するRFCスレッドでも非常に議論されました。 型理論の観点からの普遍的/存在型についてだけでなく、これが新しいユーザーにとってどのように混乱するかについても多くの議論がありました。

確かに、実際の新規ユーザーの視点は得られませんでしたが、これはどこからともなく出てきたわけではありません。

@Boscop anyとsomeは、この仕事をするためのキーワードのペアとして提案されましたが、反対に決定されました(理論的根拠がどこかに書き留められたかどうかはわかりませんが)。

確かに、さびに不慣れでタイプ理論家ではない人々からフィードバックを得ることができませんでした

そして、包含のための引数は、それが新規参入のためにそれを容易にすることを常にでした。 それで、もし私たちが今、新参者からの実際のフィードバックを関連性のある種類のフィードバックではないでしょうか?

誰かが時間があるとしたら、フォーラムや他の場所で、包含の前後で人々がどのように混乱していたかを調査することができたと思います(私は統計があまり得意ではありませんでしたが、誰かができたと確信していますブラインド予測よりも優れたものを考え出す)。

そして、インクルージョンの議論は常に、それが新参者にとってより簡単になるだろうというものでした。 それで、もし私たちが今、新参者からの実際のフィードバックを持っているなら、それは新参者がそれをどのように理解すべきかを議論するのではなく、非常に関連性のある種類のフィードバックではないでしょうか?

はい? つまり、起こったことが良い考えだったのか悪い考えだったのかについて議論しているのではありません。 RFCスレッドがこれについてフィードバックを受け取ったことを指摘するだけで、とにかく決定されました。

あなたが言ったように、フィードバックに関するメタディスカッションをどこか別の場所で行う方がおそらく良いでしょうが、それがどこにあるかはわかりません。

いいえ、実存型とユニバーサル型を指定するためにさらに2つのキーワードを追加しても、混乱は改善されません。事態はさらに悪化するだけです。

悪い? どうですか? 私は曖昧さ/混乱よりも記憶することを好みます。

はい? つまり、起こったことが良い考えだったのか悪い考えだったのかについて議論しているのではありません。 RFCスレッドがこれについてフィードバックを受け取ったことを指摘するだけで、とにかく決定されました。

もちろん。 しかし、どちらの主張も、内部で何が起こっているのかを深く理解し、彼らが所属していないグループ(新参者)について推測し、将来について推測する、古くて傷ついた経験豊富なプログラマーでした。 事実の観点からは、実際に何が起こっているかという点では、サイコロを振るよりもはるかに優れているわけではありません。 これは、専門家の不十分な経験に関するものではなく、意思決定の基礎となる適切なデータがないことに関するものです。

今ではそれが導入され、実際のハードデータ、または0から10のスケールでどれだけの人が混乱するかという土地で取得できる限りのハードデータを取得する方法があります。

あなたが言ったように、フィードバックに関するメタディスカッションをどこか別の場所で行う方がおそらく良いでしょう

たとえば、ここで、私はすでにそのような議論を開始しており、たとえ小さなものであっても、実行できる実際の手順がいくつかあります: https :

悪い? どうですか?

なぜなら、 impl Traitが非推奨にならない限り、3つすべてを持っているので、混乱に加えて覚えておく必要があります。 impl Traitがなくなると、状況は異なり、2つのアプローチの長所と短所が重くなります。

callee-pickingのようにimpl Traitで十分です。 引数の位置で使用しようとすると、混乱が生じます。 HRTBはその混乱を取り除くでしょう。

@vorner以前、私はRustの初心者を使って実際のA / Bテストを行い、Rustに精通している人として推測するのは難しいため、実際に何が簡単で習得が難しいかを確認する必要があると主張しました。
FWIW、私がRust(C ++、D、Javaなどから来ている)を学んでいたとき、全称記号型ジェネリック(構文を含む)は理解しやすかった(ジェネリックの寿命は少し難しかった)ことを覚えています。
argタイプのimpl Traitは、初心者からの多くの混乱と、このような多くの質問につながるとます。
変更によってRustを習得しやすくする証拠がない場合は、そのような変更を行うのを控え、代わりに、一貫性によって少なくとも覚えやすくなるため、Rustをより一貫性のあるものにする/維持する変更を行う必要があります。 さびた初心者はとにかく本を数回読まなければならないので、argsにimpl Traitを導入して、本のジェネリックを後でまで延期できるようにしても、実際には複雑さを取り除くことはできません。

@eddybところで、 implに加えて、型に別のexistentialキーワードが必要なのはなぜですか? (両方にsomeを使用したいのですが。)

FWIW、私がRust(C ++、D、Javaなどから来ている)を学んでいたとき、全称記号型ジェネリック(構文を含む)は理解しやすかった(ジェネリックの寿命は少し難しかった)ことを覚えています。

私自身も問題ないと思います。 私の現在の会社では、Rustクラスを率いています。今のところ、週に1回会合を持ち、実践的な実装を教えようとしています。 人々はベテランのプログラマーであり、主にJavaとScalaのバックグラウンドから来ています。 いくつかの障害がありましたが、引数の位置にあるジェネリックス(少なくともそれらを読む-実際にそれらを書くことには少し注意が必要です)は問題ではありませんでした。 ジェネリックスがリターン位置にあること(たとえば、呼び出し元が関数が返すものを選択すること)については少し驚きました。特に、それはしばしば省略できるということですが、説明はクリックするまでに2分ほどかかりました。 しかし、引数の位置にimpl Traitが存在することについても言及することを恐れています。なぜなら、なぜそれが存在するのかという質問に答える必要があるからです。そして、それに対する本当の答えはありません。 それはやる気を起こさせるのに良くありません、そして、やる気を持つことは学習プロセスにとって重要です。

それで、問題は、コミュニティは議論を裏付けるためにいくつかのデータで議論を再開するのに十分な声を持っているかということです。

@eddybところで、なぜimplに加えて、型に別の実存的なキーワードが必要なのですか? (両方に使用したいのですが。)

forall …/ meがゆっくりと忍び寄ってみませんか

我々は持っている@phaazon forall (すなわち"ユニバーサル")とを、それのfor 、例えばHRTB中( for<'a> Trait<'a> )。

@eddybうん、それから、たとえばHaskellがforallで行うように、それを実存的にも使用します。

議論全体が非常に意見が分かれており、議論のアイデアが安定して見えていることに少し驚いています。 後で別のRFCをプッシュして、それを元に戻す方法があることを願っています(これがもたらす混乱のすべてが本当に本当に好きではないので、私は完全にそれを書くつもりです)。

よくわかりません。 それらを議論の位置に置くことの意味は何ですか? Rustはあまり書きませんが、 -> impl Traitができるのが本当に好き

私の理解では、それは主に一貫性のためでした。 fnシグニチャの引数の位置にタイプimpl Traitを記述できる場合、他の場所に記述できないのはなぜですか?

そうは言っても、私は個人的には「タイプパラメータを使用するだけ」と人々に伝えたかったのです...

はい、それは一貫性のためです。 しかし、型パラメーターが非常に使いやすい場合、それが十分な引数であるかどうかはわかりません。 また、問題が発生します。

また、問題が発生します。

impl Traitでいくつかのことを表現できないことを考えると、引数の1つとしてimpl Traitで関数を使用すると、ターボフィッシュを実行できないため、そのアドレスを取得できません(いくつか忘れましたか?他の不利な点は?)、とにかくそれらを使用する必要があるので、型パラメーターに対してリントすることはほとんど意味がないと思います。

したがって、あなたはそのアドレスを取ることができません

署名から推測することで可能です。

それらを議論の位置に置くことの意味は何ですか?

特性バウンドを使用するのとまったく同じであるため、何もありません。

fn foo(x: impl Debug)

とまったく同じものです

fn foo<A>(x: A) where A: Debug
fn foo<A: Debug>(x: A)

また、これを考慮してください:

fn foo<A>(x: A) -> A where A: Debug

引数の位置にあるimpl Traitは、匿名化されているため、これを行うことはできません。 このような状況に対処するために必要なものはすべてすでに揃っているため、これはかなり役に立たない機能です。 ほとんどの人が型変数/テンプレートパラメータを知っており、Rustはそのimpl Trait構文を使用する単一の言語であるため、人々はその新機能を簡単に学ぶことはできません。 そのため、多くの人が、新しい必要なセマンティクス(つまり、呼び出し先が選択したタイプ)を導入したため、戻り値/ letバインディングにとどまる必要があると不満を漏らしています。

簡単に言うと、 @ iopq :これは必要ありません。 「非常に特殊な用途、つまり匿名型変数に対応しているため、実際には誰も必要としない別の構文糖衣構文を追加しましょう」以外の意味

また、私が言い忘れたことがあります。それは、関数がどのようにパラメーター化/単形化されているかを確認するのを非常に難しくします。

@Verner部分的なターボフィッシュでは、単純さ、読みやすさ、明確さのために、それに対してリントすることは非常に理にかなっています。 とはいえ、私は実際にはargの位置にある機能を最初から持っているわけではありません。

-> impl Traitで呼び出し先がタイプを選択し、 x: impl Traitで呼び出し元がタイプを選択する場合、どのように一貫性がありますか?

私はそれが機能する他の方法がないことを理解していますが、それは「一貫性がある」ようには見えません、それは一貫性の反対のようです

私は、それが一貫性を除いてすべてであり、人々が混乱し、新参者だけでなく、高度な熟練したさび病者になることにも本当に同意します。

このスレッドで再訴訟されている質問を解決するために、2つのRFCがあり、2年以上前から合計600近くのコメントを受け取りました。

  • rust-lang / rfcs1522 "最小impl Trait ")
  • rust-lang / rfcs1951 " impl Trait構文とパラメーターのスコープを完成させ、引数に展開する")

(これらの議論を読むと、私が最初は2つのキーワードのアプローチを強く支持していたことがわかります。今では、1つのキーワードを使用することが正しいアプローチだと思います。)

2年と数百のコメントの後、決定が下され、機能は安定しました。 これはこの機能の追跡の問題であり、 impl Traitのまだ不安定なユースケースを追跡するために開かれています。 impl Traitの解決済みの側面を再訴訟することは、この追跡の問題のトピックから外れています。 これについて引き続き話し合うことを歓迎しますが、課題追跡システムでは使用しないでください。

impl Traitが特性のfnsの引数の位置でサポートを得ていない場合、どのように安定しましたか?

@alexregサポートされています...? https://play.rust-lang.org/?gist=47b1c3a3bf61f33d4acb3634e5a68388&version=stable

@daboross次に、元の投稿のチェックボックスを

(https://play.rust-lang.org/?gist=47b1c3a3bf61f33d4acb3634e5a68388&version=stableが現在機能していることを発見しただけです)

https://play.rust-lang.org/?gist=c29e80715ac161c6dc95f96a7f91aa8c&version=stable&mode=debugが(まだ)機能しないのは奇妙だと思います。さらに、このエラーメッセージが表示されます。 このように考えるのは私だけですか? トレイトのリターン位置にimpl Traitチェックボックスを追加する必要があるかもしれません。あるいは、トレイト関数の引数位置にimpl Traitのみを許可し、 existential type使用を強制することを意識的に決定したのでしょうか。リターンタイプの

@Ekleog

トレイト関数の引数位置でimplTraitのみを許可し、戻り型に実存型の使用を強制することは、意識的な決定でしたか?

はい-トレイトの存在型を使用するより実際的な経験ができるまで、トレイトのリターン位置impl Traitは延期されました。

@cramertj私たちはまだそれを実装するのに十分な実践的な経験を持っている段階にありますか?

機能を追加する前に、いくつかの安定したリリースでimplTraitを確認したいと思います。

@ mark-im個人的には、トレイトメソッドのreturn-position impl Traitについて、リモートで物議を醸しているものがわかりません...何かが足りないのかもしれません。

物議を醸すとは思わない。 機能の追加が速すぎるように感じます。 しばらくの間、技術的負債を止めて集中し、最初に現在の機能セットを体験してみるとよいでしょう。

そうですか。 私はそれが新しい機能よりも既存の機能の欠けている部分だと思っていると思います。

@alexregは正しいと思います。トレイトのメソッドで、実存的なimpl Traitを使用するのは非常に魅力的です。 これは実際には新しい機能ではありませんが、実装する前に対処すべきことがいくつかあると思いますか?

@phaazonおそらく、そうです...実装の詳細が、現在すでにあるものとどの程度異なるかはわかりませんが、誰かがコメントする可能性があります。 let / constバインディングの実存型も見たいのですが、これを超える機能として間違いなく受け入れることができるので、開始する前にもう1サイクルほど待ちます。

特性の普遍的なimpl特性を抑えることができるかどうか疑問に思います...

しかし、ええ、私はあなたの主張を理解していると思います。

@ mark-imいいえ、できません。すでに安定しています。

それらは関数内にありますが、トレイト宣言はどうですか?

@ mark-imは、スニペットが示すように、implsとtrait宣言の両方で安定しています。

abstract type落ち着くところに追いつくために、ただ飛び込んでください。 個人的には、 @ Centrilが最近提案した構文とベストプラクティスにかなり参加しています。

// GOOD:
type Foo: Iterator<Item: Display>;

type Bar = (impl Display, impl Debug);

// BAD
type Foo = impl Iterator<Item = impl Display>;

type Bar0: Display;
type Bar1: Debug;
type Bar = (Bar0, Bar1);

これは私のコードに適用され、次のようになります。

// Concrete type with a generic body
struct Data<TBody> {
    ts: Timestamp,
    body: TBody,
}


// A name for an inferred iterator
type IterData = Data<impl Read>;
type Iter: Iterator<Item = IterData>;


// A function that gives us an iterator. Also takes some arbitrary range
fn iter(&self, range: impl RangeBounds<Timestamp>) -> Result<Iter, Error> { ... }


// A struct that holds on to that iterator
struct HoldsIter {
    iter: Iter,
}

type Bar = (impl Display,);が良いのは私には意味がありませんが、 type Bar = impl Display;は悪いでしょう。

さまざまな代替の実存型構文(すべてrfc 2071とは異なりますか?)を決定する場合、 https://users.rust-lang.org/のフォーラムスレッドがそれを行うのに適した場所でしょうか?

私は今そのようなスレッドを開始するための代替案を十分に理解していませんが、実存的なタイプはまだ実装されていないので、フォーラムでの議論とその後の新しいRFCは、追跡の問題でそれについて話すよりもおそらく良いと思います。

type Foo = impl Trait何が問題になっていますか?

@daborossおそらく代わりに内部フォーラム。 構文を完成させるために、RFCを作成することを検討しています。

@daborossこのスレッドの構文については、すでに十分な議論があります。 @Centrilがこの時点でRFCを作成できれば、

特性の存在について議論するためにサブスクライブできる問題はありますか?

いずれかの構文にマクロ関連の引数はありますか?

@tomaka最初のケースでは、 type Foo = (impl Display,)が実際に使用できる唯一の構文です。 以下のための私の好みtype Foo: Trait以上type Foo = impl Traitちょうど私たちは同じように、私たちは名前を付けることができますタイプを結合しているという事実から来ている<TFoo: Trait>またはwhere TFoo: Trait持つのに対し、 impl Traitタイプに名前を付けることはできません。

明確にするために、私はtype Foo = impl Barが悪いと言っているのではなく、 @ KodrAusの動機もあって、単純なケースではtype Foo: Bar方が

後者は「Foo型はBarを満たす」と読み、前者は「Foo型はBarを満たす型と等しい」と読みました。 したがって、前者は、私の見解では、拡張的な観点からより直接的で自然です(「Fooでできること」)。 後者を理解するには、タイプの存在記号をより深く理解する必要があります。

type Foo: Barも非常に優れています。これは、トレイトの関連する型の境界として使用される構文である場合、トレイトの宣言をimplにコピーするだけで、簡単に機能するためです(公開したいすべての情報..)。

特に、関連付けられた型の境界が含まれている場合や、関連付けられている型が多数ある場合は、構文も簡潔になります。 これにより、ノイズが減少し、読みやすさが向上します。

@KodrAus

これらの型定義の読み方は次のとおりです。

  • type Foo: Traitは、「 FooはTrait実装するタイプです」を意味します
  • type Foo = impl Traitは、「 Fooは、 Trait実装するある種のエイリアスです」という意味です。

私には、 Foo: Trait単純に制約を宣言するFoo実装Trait 。 ある意味で、 type Foo: Traitは不完全Fooの実際の定義がありません。

一方、 impl Traitは、「これは単一の型ですが、コンパイラーはその名前を理解します」ということを思い起こさせます。 したがって、 type Foo = impl Traitは、( Traitを実装する)具象型がすでにあることを意味します。そのうちのFooは単なるエイリアスです。

type Foo = impl Traitは、正しい意味をより明確に伝えていると思います。 Fooは、 Trait実装するある種のエイリアスです。

@stjepang

type Foo: Traitは、「FooはTraitを実装するタイプ」を意味します
[..]
ある意味で、 type Foo: Traitは不完全だと感じています。

これは私もそれを読む方法です(モジュロフレージング...)、そしてそれは拡張的に正しい解釈です。 これは、 Foo (型が提供する射)で何ができるかについてすべてを語って

一方、 impl Traitは、「これは単一の型ですが、コンパイラーがギャップを埋める」ことを喚起します。 したがって、 type Foo = impl Traitは、具体的な型( Traitを実装する)がすでにあることを意味します。そのうちのFooはエイリアスですが、コンパイラはそれが実際にどの型であるかを判断します。

これは、拡張の観点から冗長な表現に関係する、より詳細で内包的な解釈です。 しかし、これは内包的な意味でより完全です。

@Centril

読者、特に初心者の観点からは、拡張性の方が重要だと思います。

これは、拡張の観点から冗長な表現に関係する、より詳細で内包的な解釈です。

外延と内包の二分法は興味深いです-私はこれまでこのようにimpl Trait考えたことがありません。

それでも、私は結論について異なるように頼みます。 FWIW、私はHaskellとScalaで存在型を理解することができなかったので、私を初心者として数えます。 :) Rustのimpl Traitは、初日から非常に直感的に感じられました。これはおそらく、型で実行できることではなく、制限されたエイリアスと考えているためです。 したがって、 Fooが何であるかを知ることと、それで何ができるかを知ることの間で、私は前者を選びます。

でも、私の2cだけです。 他の人はimpl Trait異なるメンタルモデルを持っているかもしれません。

私はこのコメントに完全に同意します: type Foo: Traitは不完全だと感じています。 また、 type Foo = impl Traitは、他の場所でのimpl Trait使用に似ていると感じます。これにより、言語の一貫性と記憶力が高まります。

@joshtriplett一貫性の議論の開始については、 https: //github.com/rust-lang/rust/issues/34511#issuecomment-387238653を参照してtype Foo: Traitを許可すると、 https://github.com/rust-lang/rfcs/pull/2289と特にうまく適合しtype Foo: Iterator<Item: Display>;は、物事をきちんと均一にします。

@stjepang type Foo: Bar;の拡張的な視点では、型理論における存在記号を理解する必要はありません。 本当に理解する必要があるのは、 Foo使用すると、 Barで提供されるすべての操作を実行できるということだけです。 Fooの観点からすると、それだけでも興味深いことです。

@Centril

私はあなたがどこから来ているのか、そしてType: Trait構文をできるだけ多くの場所にプッシュすることの魅力を理解したと信じています。

:がtype-implements-trait境界に使用され、 =が型定義とtype-equals-another-type境界に使用されるという強い意味合いがあります。

これはあなたのRFCでも明らかだと思います。 たとえば、次の2つのタイプの境界を考えます。

  • Foo: Iterator<Item: Bar>
  • Foo: Iterator<Item = impl Bar>

最終的にこれらの2つの境界は同じ効果を持ちますが、(私は)微妙に異なります。 前者は「 ItemはトレイトBar実装する必要があります」と言い、後者は「 ItemはBar実装するあるタイプと等しくなければならない」と言います。

別の例を使用して、このアイデアを説明してみましょう。

trait Person {
    type Name: Into<String>; // Just a type bound, not a definition!
    // ...
}

struct Alice;

impl Person for Alice {
    type Name = impl Into<String>; // A concrete type definition.
    // ...
}

では、 Personを実装する実存型をどのように定義する必要がありますか?

  • type Someone: Person 、これはタイプバウンドのように見えます。
  • type Someone = impl Person 、これは型定義のように見えます。

@stjepang型境界のように見えることは悪いことではありません:)次のようにPerson for Alice実装できます:

struct Alice;
trait Person          { type Name: Into<String>; ... }
impl Person for Alice { type Name: Into<String>; ... }

ほら! トレイトとimplの両方の{ .. }内のものは同じです。つまり、 Nameに関する限り、トレイトからテキストをそのままコピーできます。

関連付けられた型は型レベルの関数(最初の引数はSelf )であるため、型エイリアスは0アリティに関連付けられた型と見なすことができるため、奇妙なことは何も起こりません。

最終的にこれらの2つの境界は同じ効果を持ちますが、(私は)微妙に異なります。 前者は「アイテムはトレイトバーを実装する必要があります」と言い、後者は「アイテムはバーを実装するあるタイプと等しくなければならない」と言います。

うん; 私は最初の言い回しがより的確で自然だと思います。 :)

@CentrilHeh 。 それは、抽象型を導入するにはtype Thing;だけで十分であることを意味しますか?

trait Neg           { type Output; fn neg(self) -> Self::Output; }
impl Neg for MyType { type Output; fn neg(self) -> Self::Output { self } }

@kennytm技術的には可能だと思います。 しかし、暗黙的/明示的についての考えに応じて、それが望ましいかどうかを尋ねることができます。 その特定のケースでは、次のように書くだけで技術的に十分だと思います。

trait Neg           { type Output; fn neg(self) -> Self::Output; }
impl Neg for MyType { fn neg(self) -> Self::Output { self } }

そしてコンパイラーはあなたのためにtype Output: Sized;を推測することができます(これはあなたに情報を与えない非常に興味のない境界です)。 これはもっと興味深い範囲を検討するためのものですが、プログラマーの怠惰のために、具体的なタイプが非常に単純な場合でも、低アフォーダンスAPIを促進する可能性があるため、最初の提案には含まれません:)どちらもtype Output;はありません

これをすべて読んだ後、私は@Centrilにもっと同意する傾向があると思います。 type Foo = impl Barを見ると、他のエイリアスと同様に、 Fooは特定のタイプであると思う傾向があります。 そうではありません。 この例を考えてみましょう。

type Displayable = impl Display;

fn foo() -> Displayable { "hi" }
fn bar() -> Displayable { 42 }

私見では、Displayableの宣言で=を見るのは少し奇妙ですが、fooとbarの戻り型が等しくありません(つまり、これは他の場所とは異なり、推移的ではありません)。 問題は、Fooが特定のタイプのエイリアスではなく、いくつかの特性を暗示していることです。 言い換えると、使用されるコンテキストに関係なく単一のタイプですが、例のように、そのタイプは用途によって異なる場合があります。

type Foo: Barは「不完全」だと感じる人もいます。 私にとってこれは良いことです。 ある意味でFooは不完全です。 それが何であるかはわかりませんが、 Bar満たすことはわかっています。

@ mark-im

問題は、Fooが特定のタイプのエイリアスではないということです。 言い換えると、使用されるコンテキストに関係なく単一のタイプですが、例のように、そのタイプは用途によって異なる場合があります。

うわー、それは本当に本当ですか? それは確かに私には非常に混乱するでしょう。

Displayableが単一の具象型ではなくimpl Display省略形になる理由はありますか? トレイトエイリアス(追跡の問題:https://github.com/rust-lang/rust/issues/41517)を同様の方法で使用できることを考えると、このような動作はさらに役立ちますか? 例:

trait Displayable = Display;

fn foo() -> impl Displayable { "hi" }
fn bar() -> impl Displayable { 42 }

@ mark-im

type Displayable = impl Display;

fn foo() -> Displayable { "hi" }
fn bar() -> Displayable { 42 }

これは有効な例ではありません。 RFC 2071の実存型に関する参照セクションから:

existential type Foo = impl Debug;

Fooは、モジュール全体の複数の場所でi32として使用できます。 ただし、 Fooをi32 Fooとして使用する各関数は、 i32である必要があるように、 Foo個別に制約を課す必要があります。

各実存型宣言は、少なくとも1つの関数本体またはconst / static初期化子によって制約される必要があります。 本体または初期化子は、特定の実存型に完全に制約するか、制約を課さない必要があります。

直接言及されていませんが、RFCの残りの部分が機能するために必要なのは、実存型のスコープ内の2つの関数が、その実存型の異なる具象型を判別できないことです。 これは、何らかの形式の競合するタイプエラーになります。

私のようなものを与えるだろうあなたの例を推測expected type `&'static str` but found type `i32`のリターンにbar 、以来fooすでにの具体的なタイプに設定しているでしょうDisplayableする&'static str 。

編集:あなたがその直感からこれに来ていない限り

type Displayable = impl Display;

fn foo() -> Displayable { "hi" }
fn bar() -> Displayable { 42 }

と同等です

fn foo() -> impl Display { "hi" }
fn bar() -> impl Display { 42 }

私の期待ではなく

existential type _0 = impl Display;
type Displayable = _0;

fn foo() -> Displayable { "hi" }
fn bar() -> Displayable { 42 }

これらの2つの解釈のどちらが正しいかは、 @ Centrilが書き込んでいるRFCに依存する可能性があると

問題は、Fooが特定のタイプのエイリアスではないということです。

これらの2つの解釈のどちらが正しいかは、 @ Centrilが書き込んでいるRFCに依存する可能性があると

type Displayable = impl Display;が存在する理由は、それが特定のタイプのエイリアスであるためです。
https://github.com/rust-lang/rfcs/issues/1738を参照して

@ Nemo157あなたの期待は正しいです。 :)

以下:

type Foo = (impl Bar, impl Bar);
type Baz = impl Bar;

脱糖されます:

/* existential */ type _0: Bar;
/* existential */ type _1: Bar;
type Foo = (_0, _1);

/* existential */ type _2: Bar;
type Baz = _2;

ここで、 _0 、 _1 、および_2はすべて名目上異なるタイプであるため、 Id<_0, _1> 、 Id<_0, _2> 、 Id<_1, _2> (および対称インスタンス)はすべて無人であり、 Idはreflで定義されてい

免責事項:私は(喜んで)RFCを読んでいません(しかしそれが何であるかを知っています)ので、構文で「直感的」と感じるものについてコメントすることができます。

type Foo: Trait構文の場合、次のようなことが可能であると完全に期待します。

trait Trait {
    type Foo: Display;
    type Foo: Debug;
}

where Foo: Display, Foo: Debugと同じ方法で現在可能です。

構文が許可されていない場合は、構文の問題だと思います。

ああ、Rustの構文が多ければ多いほど、それを学ぶのは難しくなると思います。 1つの構文が「習得しやすい」場合でも、2つの構文が必要である限り、初心者は最終的に両方を習得する必要があり、既存のプロジェクトに参加した場合は、おそらくより早く習得する必要があります。

@Ekleog

type Foo: Trait構文の場合、次のようなことが可能であると完全に期待します。

可能です。 これらの「タイプエイリアス」は、関連付けられたタイプを宣言します(タイプエイリアスは、0-aryタイプレベル関数として解釈できますが、関連付けられたタイプは1+ -aryタイプレベル関数です)。 もちろん、1つのトレイトに同じ名前の複数のタイプを関連付けることはできません。これは、モジュールで同じ名前の2つのタイプエイリアスを定義しようとするようなものです。 implでは、 type Foo: Barも存在記号に対応します。

ああ、Rustの構文が多ければ多いほど、それを学ぶのは難しくなると思います。

両方の構文がすでに使用されています。 type Foo: Bar;は、特性においてすでに合法であり、 Foo: Barとしての全称記号についても有効です。ここで、 Fooは型変数です。 impl Traitは、戻り位置での存在記号と引数位置での全称記号に使用されます。 言語の両方のプラグの一貫性のギャップを許可します。 また、さまざまなシナリオに最適であるため、両方を使用するとグローバルに最適になります。

さらに、初心者がtype Foo = (impl Bar, impl Baz);を必要とする可能性はほとんどありません。 ほとんどの用途はおそらくtype Foo: Bar;です。

RFC 2071の元のプルリクエストには、この議論で完全に却下されたように見えるtypeofキーワードが記載されています。 現在提案されている構文はかなり暗黙的であり、コンパイラーとコードを読み取る人間の両方が具体的な型を検索していることがわかります。

これを明示的にしたいのですが。 だから代わりに

type Foo = impl SomeTrait;
fn foo_func() -> Foo { ... }

私たちは書くだろう

fn foo_func() -> impl SomeTrait { ... }
type Foo = return_type_of(foo_func);

(バイクシェッドされるreturn_type_ofの名前で)、または

fn foo_func() -> impl SomeTrait as Foo { ... }

これは新しいキーワードさえ必要とせず、implTrait構文を知っている人なら誰でも簡単に理解できます。 後者の構文は簡潔で、すべての情報が1か所にあります。 特性の場合、次のようになります。

trait Bar
{
    type Assoc: SomeTrait;
    fn func() -> Assoc;
}

impl Bar for SomeType
{
    type Assoc = return_type_of(Self::func);
    fn func() -> Assoc { ... }
}

あるいは

impl Bar for SomeType
{
    fn func() -> impl SomeTrait as Self::Assoc { ... }
}

これがすでに議論されて却下された場合は申し訳ありませんが、私はそれを見つけることができませんでした。

@Centril

可能です。 これらの「タイプエイリアス」は、関連付けられたタイプを宣言します(タイプエイリアスは、0-aryタイプレベル関数として解釈できますが、関連付けられたタイプは1+ -aryタイプレベル関数です)。 もちろん、1つのトレイトに同じ名前の複数のタイプを関連付けることはできません。これは、モジュールで同じ名前の2つのタイプエイリアスを定義しようとするようなものです。 暗黙的に、Foo:Barと入力すると、存在記号にも対応します。

(申し訳ありませんが、 impl Trait for Structではなくtrait Trait )

申し訳ありませんが、わかりません。 私が言おうとしているのは、私にとっては次のようなコードです

impl Trait for Struct {
    type Type: Debug;
    type Type: Display;

    fn foo() -> Self::Type { 42 }
}

(フルバージョンの遊び場リンク)
それがうまくいくはずだと感じています。

それだけで2つの境界を入れているので、 Typeと同じように、 where Type: Debug, Type: Display仕事。

これが許可されない場合(「もちろん、1つの特性に同じ名前の複数の関連付けられた型を含めることはできません」で理解できるようですが、 impl Trait for Struct代わりにtrait Trait書くときにエラーが発生しましたimpl Trait for Structわかりません)、それはtype Type: Trait構文の問題だと思います。

次に、 trait宣言内で、構文はすでにtype Type: Traitであり、複数の定義を許可していません。 だから多分このボートはずっと前にすでに出航していると思います…

ただし、 @ stjepangと@joshtriplettが上で指摘したように、 type Type: Traitは不完全だと感じます。 また、 trait宣言では意味があるかもしれませんが(実際には不完全になるように設計されていますが、複数の定義を許可しないのは奇妙です)、 impl Traitブロックでは意味がありません。 、タイプは確実にです(現在はtype Type = RealTypeしか記述できません)

impl Traitは、戻り位置での存在記号と引数位置での全称記号に使用されます。

はい、これを書くときに引数の位置にあるimpl Traitについても考え、安定化が行われていることを知っていれば、引数の位置にあるimpl Traitに対して同じ引数をサポートしたかどうか疑問に思いました。 そうは言っても、この議論に再び火をつけないほうがいいと思います:)

言語の両方のプラグの一貫性のギャップを許可します。 また、さまざまなシナリオに最適であるため、両方を使用するとグローバルに最適になります。

最適でシンプル

ええと、単純さを優先して最適を失うことは良いことだと思います。 同様に、CとMLはほぼ同時に生まれました。 Cは単純さを優先して最適に大きな譲歩をしました、MLは最適にはるかに近かったが、はるかに複雑でした。 これらの言語の派生語を数えても、C開発者とML開発者の数は同じではないと思います。

impl Traitおよび:

現在、 impl Traitと:構文の周りで、同じ機能セットに対して2つの代替構文の両方を作成する傾向があるように感じます。 ただし、同じ機能に対して2つの構文を使用すると、ユーザーを混乱させるだけであるため、これは良いことではないと思います。特に、正確なセマンティクスが常に微妙に異なる場合はなおさらです。

type Type: Traitが最初のtype Type = impl Trait来るのをいつも見た初心者を想像してみてください。 彼らはおそらく何が起こっているのかを推測することができますが、「WTFはそれですか? 私はRustを何年も使用していますが、まだ見たことのない構文がありますか?」 これは多かれ少なかれC ++が陥った罠です。

機能の肥大化

私が考えているのは、基本的に、機能が多ければ多いほど、言語を学ぶのが難しくなるということです。 そして、 type Type = impl Trait type Type: Traitを使用することの大きな利点はわかりません。たとえば、6文字節約できますか?

持つrustc出力を見誤りtype Type: Traitを使用するためにそれを書いている人と言うtype Type = impl Trait私にははるかに理にかなっては:少なくとも物事を書いて、単一の方法があります、それはすべての人にとって理にかなっており( impl Traitはすでにリターンポジションに存在するものとして明確に認識されています)、すべてのユースケースをカバーしています。 そして、人々が直感的だと思うものを使おうとすると(私はそれに同意しませんが、私にとっては= impl Traitは現在の= i32と比較してより直感的です)、彼らは正しくリダイレ​​クトされます従来の正しい書き方。

RFC 2071の元のプル要求は、この議論で完全に却下されたように見えるtypeofキーワードに言及しています。 現在提案されている構文はかなり暗黙的であり、コンパイラーとコードを読み取る人間の両方が具体的な型を検索していることがわかります。

typeofは、1。5年前に開いた問題で簡単に説明されました: https :

初心者として言えば、 type Foo: Bar構文がわかりにくいと思います。 これは関連する型の構文ですが、構造体ではなく、特性にあると想定されています。 impl Trait一度見たら、それが何であるかを理解することができます。そうでなければ、それを調べることができます。 他の構文でそれを行うのは難しく、利点が何であるかはわかりません。

言語チームの一部の人々は、存在型に名前を付けるためにimpl Traitを使用することに本当に反対しているように思われるので、代わりに他のものを使用したいと考えています。 ここで、私にはほとんど意味がありません。

とにかく、この馬は殴られて死んだと思います。 構文についてはおそらく何百ものコメントがあり、ほんの一握りの提案しかありません(私は事態を悪化させているだけだと思います)。 構文がすべての人を幸せにするわけではないことは明らかであり、それらすべてに賛成と反対の議論があります。 たぶん私たちはただ1つを選んでそれに固執するべきです。

うわー、それは私が理解したことではまったくありません。 私をまっすぐにしてくれてありがとう@ Nemo157 !

その場合、私は確かに=構文を好みます。

@Ekleog

それがtype Type: Trait構文の問題だと思います。

それは許可される可能where Type: Foo + Bar代わりにwhere Type: Foo, Type: Bar where Type: Foo + Bar書くので、あまり良い考えではないようです。 また、関連付けられたタイプの場合は代わりにFoo + Barを書き込むことを提案する、この場合の適切なエラーメッセージを簡単に発生させることができます。

type Foo = impl Bar;は、 = impl Barが表示され、 -> impl Barとして使用されるたびにそれを置き換えることができると結論付けるという、理解しやすさの問題もあります。 しかし、それはうまくいきません。 @ mark-imがこの解釈を行いましたが、これは間違いである可能性がはるかに高いようです。 したがって、私はtype Foo: Bar;が学習可能性のためのより良い選択であると結論付けます。

ただし、 @ stjepangと@joshtriplettで指摘されているように、Type:Traitと入力すると不完全な感じがします。

拡張POVからは不完全ではありません。 type Foo = impl Bar;から取得するのとまったく同じ量の情報をtype Foo: Bar;から取得します。 したがって、 type Foo: Bar;で何ができるかという観点からすると、これで完了です。 実際、後者はtype _0: Bar; type Foo = _0;として脱糖されています。

編集:私が意味したのは、一部の人にとっては不完全に感じるかもしれませんが、技術的な観点からではないということでした。

そうは言っても、この議論に再び火をつけないほうがいいと思います:)

良い考えです。 意図したとおりにではなく、設計時に言語をそのまま考慮する必要があります。

ええと、単純さを優先して最適を失うことは良いことだと思います。

簡単にするために、代わりにtype Foo = impl Bar;削除します。
Cの想定される単純さ(Haskell Coreや同様のものは、まだ健全でありながらおそらくより単純であるため、想定される)は、表現度と健全性に関しては高額であることに注意してください。 Cは言語デザインの私の北の星ではありません。 それから遠く離れています。

現在、 impl Traitと:構文の周りで、同じ機能セットに対して2つの代替構文の両方を作成する傾向があるように感じます。 ただし、同じ機能に対して2つの構文を使用すると、ユーザーを混乱させるだけであるため、これは良いことではないと思います。特に、正確なセマンティクスが常に微妙に異なる場合はなおさらです。

ただし、セマンティクスはまったく異なりません。 一方が他方に脱糖します。
type Foo: Bar;またはtype Foo = impl Barどちらか一方だけが機能しないように書き込もうとすることの混乱は、両方が完全に明確に定義されたセマンティクスを持っていても、ユーザーの邪魔になるだけだと思います。 ユーザーがtype Foo = impl Bar;を書き込もうとすると、lintが起動し、 type Foo: Bar;を提案します。 lintは、他の構文についてユーザーに教えています。
私にとって、言語が統一され、一貫していることが重要です。 どこかで両方の構文を使用することにした場合は、その決定を一貫して適用する必要があります。

type Type: Traitが最初のtype Type = impl Trait来るのをいつも見た初心者を想像してみてください。

この特定のケースでは、lintが起動し、前者の構文を推奨します。 type Foo = (impl Bar, impl Baz);に関しては、初心者はいずれにせよ-> impl Traitを学ぶ必要があるので、そこから意味を推測できるはずです。

これは多かれ少なかれC ++が陥った罠です。

C ++の問題は主に、それがかなり古く、Cの手荷物があり、多くの機能があまりにも多くのパラダイムをサポートしていることです。 これらは別個の機能ではなく、構文が異なるだけです。

私が考えているのは、基本的に、機能が多ければ多いほど、言語を学ぶのが難しくなるということです。

新しい言語を学ぶことは、主にその重要な図書館を学ぶことだと思います。 それはほとんどの時間が費やされる場所です。 適切な機能により、ライブラリの構成がはるかに容易になり、より多くの場合に機能するようになります。 私は、あなたに低レベルで考えさせ、重複を引き起こす言語よりも、優れた抽象力を与える言語を好みます。 この場合、より抽象的な力を追加したり、実際には機能さえ追加したりすることはなく、人間工学を向上させるだけです。

そして、 type Type = impl Trait:もtype Type: Traitを使用することの大きな利点はわかりません。たとえば、6文字節約できますか?

はい、6文字しか保存されていません。 しかし、 type Foo: Iterator<Item: Iterator<Item: Display>>;を考慮すると、代わりに次のようになります。 type Foo = impl Iterator<Item = impl Iterator<Item = impl Display>>;これはノイズがはるかに多くなります。 type Foo: Bar;は、後者に比べてより直接的であり、誤解(再置換..)の傾向が少なく、関連するタイプ(特性からタイプをコピーする..)に対してより適切に機能します。
さらに、 type Foo: Barは自然にtype Foo: Bar = ConcreteType;に拡張できます。これにより、具象型が公開されますが、 Bar満たすことも保証されます。 type Foo = impl Trait;そのようなことはできません。

rustcがtype Type: Traitを表示するときに、 type Type = impl Traitを使用するように書いている人が、私にははるかに理にかなっているというエラーを出力することは、私にとってはるかに理にかなっています。

彼らはそれを書くための従来の正しい方法に正しくリダイレ​​クトされます。

私は物事を書くための1つの従来の方法があることを提案しています。 type Foo: Bar; 。

@lnicola

初心者として言えば、 type Foo: Bar構文がわかりにくいと思います。 これは関連する型の構文ですが、構造体ではなく、特性にあると想定されています。

繰り返しますが、型エイリアスは実際には関連する型と見なすことができます。 あなたは言うことができるでしょう:

trait Foo        { type Baz: Quux; }
// User of `Bar::Baz` can conclude `Quux` but nothing more!
impl Foo for Bar { type Baz: Quux; }

// User of `Wibble` can conclude `Quux` but nothing more!
type Wibble: Quux;

関連するタイプとタイプエイリアスでまったく同じように機能することがわかります。

はい、6文字しか保存されていません。 しかし、 type Foo: Iterator<Item: Iterator<Item: Display>>;を検討すると、代わりに次のようになります。 type Foo = impl Iterator<Item = impl Iterator<Item = impl Display>> ; ノイズが多いです。

これは、名前付き実存を宣言するための構文と直交しているようです。 私が提案されたことを覚えている4つの構文はすべて、これを次のように許可する可能性があります。

type Foo: Iterator<Item: Iterator<Item: Display>>;
type Foo = impl Iterator<Item: Iterator<Item: Display>>;
existential type Foo: Iterator<Item: Iterator<Item: Display>>;
existential type Foo = impl Iterator<Item: Iterator<Item: Display>>;

Trait<AssociatedType = impl Bound>構文の代わりに提案された省略形Trait<AssociatedType: Bound>を使用して、実存型(名前付きまたは匿名)の関連する型の匿名実存型を宣言できることは、独立した機能です(ただし、実存型機能のセット全体の一貫性を保つための条件)。

@ Nemo157それらは異なる機能です。 しかし、一貫性を保つために、それらを一緒に検討するのは自然なことだと思います。

@Centril

申し訳ありませんが、間違っています。 拡張POVからは不完全ではありません。

提案された構文に情報が不足していることを示唆したことはありません。 私はそれが不完全だと

また、このスレッドでは、人々がこの正確な構文の違いで解釈の問題を示したことにも注意してください。 type Foo = impl Traitは、 Fooが、別の具体的なタイプをとることができる特性のエイリアスではなく、何度使用しても、特定の名前のない具体的なタイプであることを明確にしているように感じますあなたがそれを使うたびに。

私はそれは彼らが知っているすべてのもの取ることができることを人々に伝えるのに役立ちますだと思う-> impl Traitし、それらを適用type Foo = impl Trait ; 一般化された概念impl Traitがあり、両方の場所でビルディングブロックとして使用されていることがわかります。 type Foo: Traitような構文は、その一般化されたビルディングブロックを非表示にします。

@joshtriplett

私はそれが不完全だと感じていることを示唆していました。 それは私にとっても他の人にとっても間違っているように見えます。

大丈夫; ここではincompleteは異なる用語を使用することを提案します。これは、私には情報が不足していることを示唆しているためです。

また、このスレッドでは、人々がこの正確な構文の違いで解釈の問題を示したことにも注意してください。

私が観察したのは、 type Foo = impl Bar;意味について、スレッドで行われた解釈の誤りFooさまざまな使用法を、名目上同じタイプではなく、むしろ異なるタイプであると解釈しました。 つまり、正確には、 「使用するたびに異なる具象型をとることができる特性のエイリアス」です。

type Foo: Bar;は紛らわしいと言う人もいますが、 type Foo: Bar;の別の解釈が、意図した意味と何が違うのかわかりません。 別の解釈について聞いてみたいと思います。

@Centril

繰り返しますが、型エイリアスは実際には関連する型と見なすことができます。

それらは可能ですが、現在、関連付けられているタイプは特性に関連しています。 impl Traitはどこでも、またはほとんど機能します。 impl Traitを一種の関連タイプとして提示する場合は、2つの概念を同時に導入する必要があります。 つまり、 impl Traitを関数の戻り型として表示し、それが何であるかを推測または読み取ると、型エイリアスにimpl Traitれたときに、その知識を再利用できます。

これを、特性定義で関連するタイプを確認することと比較してください。 その場合、他の構造体が定義または実装しなければならないものであることがわかります。 しかし、特性の外でtype Foo: Debug出くわした場合、それが何であるかはわかりません。 それを実装する人は誰もいないので、それはある種の前方宣言ですか? C ++の場合のように、継承と関係がありますか? 他の誰かがタイプを選択するMLモジュールのようなものですか? また、以前にimpl Traitを見たことがあれば、それらの間にリンクを作成することはできません。 私たちは、書き込みfn foo() -> impl ToString 、ないfn foo(): ToString 。

タイプFoo = impl Bar; また、= impl Barが表示され、-> implBarとして使用されるたびに置換できると結論付けるという理解しやすさの問題もあります。

ここで前に言いましたが、それはlet x = foo();がfoo()代わりにx使用できることを意味すると考えるようなものです。 いずれにせよ、それは誰かが必要なときにすぐに調べることができる詳細ですが、概念を根本的に変えることはありません。

つまり、それがどのように機能するか(定義が矛盾している場合に何が起こるか)が正確にわからなくても、これが何であるか( -> impl Traitような推定型)を簡単に理解できます。 他の構文では、それが何であるかさえ理解するのは難しいです。

@Centril

大丈夫; 私には、情報が不足していることを示唆しているため、ここでは不完全とは異なる用語を使用することを提案します。

「不完全」とは、情報が不足していることを意味する必要はありません。何かが他の何かを持っているはずであり、持っていないように見えることを意味する場合があります。

type Foo: Trait;は完全な宣言のようには見えません。 何かが足りないようです。 そして、それはtype Foo = SomeType<X, Y, Z>;とは不幸にも異なっているようです。

おそらく、私たちのワンライナーだけでは、 type Inferred: Traitとtype Inferred = impl Trait間のこのコンセンサスギャップを実際に埋めることができないという点に到達しています。

この機能の実験的な実装を任意の構文(RFCで指定されているものでも)と組み合わせて、より大きなプログラムで試してみて、コンテキストにどのように適合するかを確認する価値があると思いますか?

@lnicola

[..] impl Traitはどこでも、またはほとんど機能します

ええと、 Foo: Boundもほとんどどこでも機能します;)

しかし、特性の外でtype Foo: Debug出くわした場合、それが何であるかはわかりません。

特性-> impl->型エイリアスでの使用の進歩は学習に役立つと思います。
さらに、「Fooがデバッグを実装するタイプ」からの推論は可能性が高いと思います
特性と一般的な境界からtype Foo: Debugを見ると、それも正しいです。

C ++の場合のように、継承と関係がありますか?

Rustの継承の欠如は、これがRustの非常に基本的なものであるため、ここで説明している機能を学習するときよりもはるかに早い段階で学習する必要があると思います。

他の誰かがタイプを選択するMLモジュールのようなものですか?

呼び出し元(ユーザー)がタイプを選択するarg: impl Barにより、 type Foo = impl Bar;に対してもその推論を行うことができます。 私には、ユーザーがタイプを選択するという推論は、 type Foo: Bar;可能性が低いように思われます。

ここで前に言いましたが、それはlet x = foo();がfoo()代わりにx使用できることを意味すると考えるようなものです。

言語が参照透過性である場合は、 foo()代わりにxを使用できます。 type Foo = impl Foo;をシステムに追加するまで、型エイリアスは参照透過性があります。 逆に、結合すでにあるかどうx = foo()利用できるが、他の、 foo()ではと交換可能ですx 。

@joshtriplett

「不完全」とは、情報が不足していることを意味する必要はありません。何かが他の何かを持っているはずであり、持っていないように見えることを意味する場合があります。

けっこうだ; しかし、そうではないということは何を持っていると思われますか?

type Foo: Trait;は完全な宣言のようには見えません。

私には完全に見えます。 FooがTraitを満たしているという判断のように見えますが、これはまさに意図された意味です。

@Centrilにとって、「何かが足りない」というのは、これがエイリアスである実際のタイプです。 それは以前の私の混乱に少し関係しています。 そのような型がないわけではなく、その型が匿名であるというだけです... =を使用すると、型があり、常に同じ型であることが微妙に意味されますが、名前を付けることはできません。

しかし、私たちはこれらの議論をやや使い果たしていると思います。 両方の構文を実験的に実装して、何が最も効果的かを確認するのは素晴らしいことです。

@ mark-im

@Centrilにとって、「何かが足りない」というのは、これがエイリアスである実際のタイプです。 それは以前の私の混乱に少し関係しています。 そのような型がないわけではなく、その型が匿名であるというだけです... =を使用すると、型があり、常に同じ型であることが微妙に意味されますが、名前を付けることはできません。

それは私にとってもまさにそのように感じます。

2つの延期された項目にすぐに取り組む可能性に加えて、生涯のエリジオンに関する問題はありますか? 自分でやるけど、どうしたらいいのかわからない!

impl Trait何を意味するのかについては、まだ多くの混乱があり、それはまったく明らかではありません。 impl Traitの正確なセマンティクスが明確にわかるまで、延期されたアイテムは間違いなく待つ必要があると思います(これはまもなく登場するはずです)。

@varkorどのセマンティクスが不明確ですか? impl Traitのセマンティクスについては、RFC 1951以降変更されておらず、2071年に拡張されています。

@alexreg計画はありませんでしたが、大まかな概要は次のとおりです。解析が追加されたら、実存的なimpl内でstaticとconstのタイプを下げる必要があります。特性コンテキストは、関数の戻りタイプに対してここで行わ。 。 しかし、あなたがしたいよDefIdでImplTraitContext::Existentialあなたが望んでいないため、オプションのimpl Trait親関数の定義からジェネリックをピックアップします。 それはあなたにまともな方法を与えるはずです。 @ oli-obkの実存型PRに基づいていると、より簡単な時間が得られる可能性があります。

@cramertj :言語でのimpl Traitのセマンティクスは、関数シグネチャでの使用に完全に制限されており、他の位置に拡張することが明白な意味を持つというのは真実ではありません。 会話のほとんどが行われているように見える、これについてもう少し詳しく説明します。

@varkor

言語でのimplTraitのセマンティクスは、関数シグネチャでの使用に完全に制限されており、他の位置に拡張することが明白な意味を持つというのは真実ではありません。

その意味はRFC2071で指定されてい

@cramertj :RFC 2071の意味はあいまいであり、「存在するタイプ」というフレーズがそこで意味するものの複数の解釈を許可します。

TL; DR — impl Trait正確な意味を設定しようとしました。これは、少なくとも直感的には不明確だった詳細を明確にすると思います。 新しいタイプのエイリアス構文の提案とともに。

Rustの存在型(投稿)


過去数日間、 impl Traitの正確な(つまり、形式的で理論的な)セマンティクスについて、Discordrust-langチャットで多くの議論が行われてきました。 機能についての多くの詳細と、それが何であるか、そして何でないかを正確に明らかにすることは有益だったと思います。 また、タイプエイリアスに対してどの構文が妥当であるかについても明らかにします。

私はいくつかの結論の要約を少し書きました。 これはimpl Trait解釈を提供しますが、これはかなりクリーンだと思います。引数位置impl Traitと戻り位置impl Traitの違いを正確に説明しています(これは「全称記号」ではありません。定量化された」対「存在的に定量化された」)。 いくつかの実際的な結論もあります。

その中で、私は「既存の型エイリアス」の一般的に述べられている要件を満たす新しい構文を提案します。
type Foo: Bar = _;

とても複雑なトピックなので、最初に明確にする必要があることがたくさんあるので、別の投稿として書きました。 フィードバックは大歓迎です!

Rustの存在型(投稿)

@varkor

RFC 2071はあいまいであり、「存在するタイプ」というフレーズがそこで意味するものの複数の解釈を許可します。

どのように曖昧ですか? 私はあなたの投稿を読みました-私はまだ静力学と定数における非動的な存在の1つの意味しか知りません。 アイテムごとに新しい実存型定義を導入することにより、戻り位置impl Traitと同じように動作します。

type Foo: Bar = _;

この構文についてはRFC2071で説明しました。そこで述べたように、 Fooが単一の推論型であり、現在のモジュールの外部に存在する非推論型の余地があることを明確に示しているのが好きです(例: type Foo: Bar = u32; )。 私はそれの2つの側面が嫌いでした:(1)キーワードがないため検索が難しく、(b) type Foo = impl Traitと比較してabstract type Foo: Bar;構文と同じ冗長性の問題があります持っている: type Foo = impl Iterator<Item = impl Display>;はtype Foo: Iterator<Item = MyDisplay> = _; type MyDisplay: Display = _;ます。 これらのどちらも取引を妨げるものではないと思いますが、いずれかのIMOで明確な勝利を収めることはできません。

@cramertjあいまいさがここに現れます:

type Foo = impl Bar;
fn f() -> Foo { .. }
fn g() -> Foo { .. }

Fooが実際に実存型の型エイリアスである場合、 fとgは異なる具象戻り型をサポートします。 何人かの人々は本能的にその構文をこのように読んでおり、実際、RFC 2071構文の議論の一部の参加者は、それが最近のDiscordの議論の一部として提案がどのように機能するかではないことに気づいただけです。

問題は、特に引数の位置impl Traitに直面して、存在記号がどこに行くのかがまったく明確ではないということです。 議論については、それは厳密に範囲が定められています。 戻り位置については、範囲が狭いtype Foo = impl Bar 、両方のポジションが妥当です。 _ベースの構文は、「実存的」さえも含まない解釈に近づき、この問題をきちんと回避します。

Fooが実際に実存型の型エイリアスである場合

(私の強調)。 「an」を「特定の」と読みました。これは、 fとgは、同じ実存型を参照しているため、異なる具象の戻り型をサポートしないことを意味します。 私はいつもtype Foo = impl Bar; let foo: impl Bar;と同じ意味を使用している、つまり新しい匿名の実存型を導入しているのを見てきました。 あなたの例を同等にする

existential type _0: Bar;
type Foo = _0;
fn f() -> Foo { .. }
fn g() -> Foo { .. }

私はそれが比較的明白であることを望みます。


1つの問題は、「タイプエイリアスのimpl Trait 」の意味がRFCで指定されていないことです。 これはRFC2071の「Alternatives」セクションで簡単に言及されてい

また、型エイリアスはすでに参照透過性ではないという言及を見たような気がします。 u.rl.oにあったと思いますが、検索してもディスカッションが見つかりませんでした。

@cramertj
@rpjohnstのポイントからimpl Traitのセマンティクスには複数の解釈があります。これらはすべて、署名での現在の使用法と一致していますが、 impl Traitを他に拡張すると異なる結果になります。場所(投稿に記載されているもの以外の2つは知っていますが、まだ議論の準備ができていません)。 そして、投稿の解釈が必ずしも最も明白であるというのは本当ではないと思います(私は個人的にその観点からAPITとRTIPについて同様の説明を見ませんでした)。

type Foo: Bar = _;に関しては、おそらくもう一度議論する必要があると思います。古いアイデアを新鮮な目で再検討しても害はありません。 それに関するあなたの問題に関して:
(1)キーワードはありませんが、どこでも型推論と同じ構文です。 ドキュメントで「アンダースコア」/「アンダースコアタイプ」などを検索すると、型推論に関するページが簡単に見つかります。
(2)はい、そうです。 私たちはこれに対する解決策を考えてきました。これはアンダースコア表記とうまく一致すると思います。これはすぐに提案できるようになることを願っています。

@cramertjのように、私はここで議論を見ていません。

@varkorの投稿で説明されている根本的なあいまいさは@ varkorの投稿にあるように)「少なくとも1つの型が存在する」ではなく、「...固有の型が存在する」と常に解釈してきたと思います。後者は「ユニバーサルタイプ」と同等であるため、その解釈を許可するつもりであれば、「存在タイプ」という句はまったく役に立たないでしょう。 この主題に関するすべてのRFCは、常に普遍的なタイプと実存的なタイプが2つの異なるものであると想定してきました。 実際の型理論ではそれが意味し、同型写像は非常に数学的に現実的ですが、私にとっては、型理論の用語を誤用しており、これには他の専門用語を選択する必要があるという議論であり、 impl Traitの意図されたセマンティクスは常に不明確であり、再考する必要があります。

@rpjohnstが説明するスコープのあいまいさは深刻な問題ですが、提案されたすべての構文は、タイプalisまたは関連するタイプのいずれかと混同される可能性があります。 これらの混乱のどちらが「悪い」か「より可能性が高い」かは、正確には、数百のコメントの後で解決できなかった終わりのない自転車小屋です。 type Foo: Bar = _;は、わずかに重要な存在を宣言するためにいくつかのステートメントの爆発が必要であるというtype Foo: Bar;の問題を修正するように見えるのが好きですが、それだけでは実際に変更するのに十分ではないと思います「決して自転車小屋」の状況。

私が確信しているのは、「 type 」の構文はすべて誤解を招くため、最終的にはtype以外のキーワードが必要になるということです。 実際、構文でtypeを使用しないでください。そのため、誰かが「型エイリアスですが、どういうわけかもっと存在している」と見なす方法はありません。

existential Foo = impl Trait;
fn f() -> Foo { .. }
fn g() -> Foo { .. }
existential Foo: Trait;
fn f() -> Foo { .. }
fn g() -> Foo { .. }



md5-b59626c5715ed89e0a93d9158c9c2535



existential Foo: Trait = _;
fn f() -> Foo { .. }
fn g() -> Foo { .. }

fとgがTrait実装する2つの異なるタイプを返す可能性があるという誤解を完全に_防止_することは私には明らかではありませんが、これは防止に近いと思います私たちはおそらく得ることができました。

@Ixrec
「存在するタイプ」というフレーズは、特にスコープのあいまいさのために問題があります。 APITとRPITのスコープがまったく異なることを他の人が指摘しているのを見たことがありません。 これは、 type Foo = impl Barような構文( impl Barが「存在型」)が本質的にあいまいであることを意味します。

はい、型理論の用語は誤用されています。 しかし、RFCでは誤用されています(または少なくとも説明されていません)。そのため、RFC自体に起因するあいまいさがあります。

@rpjohnstが説明するスコープのあいまいさは深刻な問題ですが、提案されたすべての構文は、タイプalisまたは関連するタイプのいずれかと混同される可能性があります。 これらの混乱のどちらが「悪い」か「より可能性が高い」かは、正確には、数百のコメントの後で解決できなかった終わりのない自転車小屋です。

いいえ、これは真実ではないと思います。 この混乱のない一貫した構文を思い付くことが可能です。 私は自転車の脱落を思い切って考えます。それは、現在の2つの提案が悪いためであり、実際には誰も満足させないからです。

私が確信しているのは、最終的に得られる構文には、 type以外のキーワードが必要であるということです。

これも必要ないと思います。 あなたの例では、まったく新しい表記法を発明しました。これは、言語設計では可能な限り避けたいものです。そうしないと、一貫性のない構文でいっぱいの巨大な言語を作成します。 より良いオプションがない場合にのみ、完全に新しい構文を検討する必要があります。 そして、私は、より良い選択肢があると主張します。

余談ですが、「実存的なタイプ」から完全に離れて、状況全体を明確にすることは可能だと思います。これについては、私または他の誰かがすぐにフォローアップします。

type以外の構文も役立つと思います。これは、多くの人がtypeを単純な置換可能なエイリアスとして解釈するためです。これは、「毎回異なるタイプになる可能性がある」という解釈を意味します。

APITとRPITのスコープがまったく異なることを他の人が指摘しているのを見たことがありません。

スコーピングは常にimplTrait提案の明示的な部分であると思ったので、「指摘」する必要はありませんでした。 スコーピングについてあなたが言ったことはすべて、過去のRFCですでに受け入れていることを繰り返しているようです。 構文からは誰にも明らかではなく、それは問題だと思いますが、これまで誰もこれを理解していなかったわけではありません。 実際、RFC 2701に関する議論の大部分は、型推論とは何か、そして見ることが許されていないという意味で、 type Foo = impl Trait;のスコーピングがどうあるべきかについてのすべてだと思いました。

この混乱のない一貫した構文を思い付くことが可能です。

type Foo: Bar = _;がその構文であると言いたいのですか、それともまだ見つかっていないと思いますか?

創造性が不十分なためではなく、ほとんどのプログラマーが型理論家ではないため、同様の混乱がない構文を思いつくことはできないと思います。 おそらく、混乱を許容できるレベルまで減らす構文を見つけることができます。確かに、型理論のベテランにとって明白な構文はたくさんありますが、混乱を完全に排除することはできません。

まったく新しい表記法を発明しました

あるキーワードを別のキーワードに置き換えただけだと思いました。 私が意図していなかった追加の変更が表示されていますか?

考えてみると、これまで「存在」を誤用してきたので、 existential Foo: Trait / = impl Traitおそらくもはや正当な構文ではないことを意味します。

したがって、外部コードが不明なタイプを参照する名前の前に新しいキーワードを配置する必要があります...これに空白を描画しています。 alias 、 secret 、 internalなどはすべてかなりひどいようで、 typeよりも「一意性の混乱」が少ないとは考えられません。

考えてみると、これまで「存在」を誤用してきたので、 existential Foo: Trait / = impl Traitおそらくもはや正当な構文ではないことを意味します。

はい、私は完全に同意します—「実存的」という用語から完全に離れる必要があると思います*( impl Traitうまく説明しながら、これを行う方法についていくつかの暫定的なアイデアがあります)。

*(おそらくdyn Traitのみの期間を予約します)

@ joshtriplett 、 @ Ixrec : _表記は、以前と同じ程度に置き換えることができなくなったことを意味することに同意します。それを維持することが優先される場合は、別の構文が必要になります。

_は、とにかく置換に関してすでに特殊なケースであることに注意してください。これが影響するのはタイプエイリアスだけではありません。現在_使用できる場所では、完全な参照透過性が妨げられています。

とにかく、_は置換に関してすでに特殊なケースであることに注意してください。これが影響するのはタイプエイリアスだけではありません。現在_を使用できる場所では、完全な参照透過性が妨げられています。

これが正確に何を意味するのかを教えていただけますか? _の影響を受ける「参照透過性」の概念を知りません

_表記は、以前と同じ程度に置き換えることができなくなったことを意味することに同意します。それを維持することが優先される場合は、別の構文が必要になります。

それが_優先度_かどうかはわかりません。 私にとって、ある構文を他の構文よりも好むように思われたのは、私たちがこれまでに見つけた唯一の客観的な議論でした。 しかし、それはすべて、 typeを置き換えるために考え出すことができるキーワードに基づいて変わる可能性があります。

これが正確に何を意味するのかを教えていただけますか? _の影響を受ける「参照透過性」の概念を知りません

ええ、すみません、私はそれらを説明せずに言葉を投げています。 私の考えを集めさせてください、そして私はよりまとまりのある説明を定式化します。 これは、 impl Traitを調べるための代替の(そして潜在的により役立つ)方法とうまく適合します。

参照透過性とは、セマンティクスを変更することなく、その定義を参照に置き換えることができ、その逆も可能であることを意味します。 Rustでは、これは明らかにfnの期間レベルでは当てはまりません。 例えば:

fn foo() -> usize {
    println!("ey!");
    42
}

fn main() {
    let bar = foo();
    let baz = bar + bar;
}

bar各オカレンスをfoo() ( barの定義)に置き換えると、明らかに異なる出力が得られます。

ただし、タイプエイリアスの場合、現時点では参照透過性が保持されます(AFAIK)。 エイリアスがある場合:

type Foo = Definition;

次に、プログラムのセマンティクスを変更せずに、 FooのオカレンスをDefinitionに置き換え、 DefinitionのオカレンスをFooに置き換えることができます(キャプチャ回避)。 、またはその型の正しさ。

紹介:

type Foo = impl Bar;

Fooが出現するたびに同じタイプになるということは、次のように記述した場合を意味します。

fn stuff() -> Foo { .. }
fn other_stuff() -> Foo { .. }

Fooオカレンスをimpl Bar置き換えることはできません。その逆も同様です。 つまり、次のように記述します。

fn stuff() -> impl Bar { .. }
fn other_stuff() -> impl Bar { .. }

リターンタイプはFooと統合されません。 したがって、RFC 2071のセマンティクスを内部に含むimpl Traitを導入することにより、型エイリアスの参照透過性が失われます。

参照透過性とtype Foo = _; 、続きます...(@ varkorによる)

多くの人が型を単純な代替可能なエイリアスとして解釈するため、型以外の構文も役立つと思います。これは、「毎回異なる型になる可能性がある」という解釈を意味します。

いい視点ね。 しかし、 = _割り当てビットは、それが単一のタイプであることを意味しませんか?

これは前に書いたことがありますが...

参照透過性:Cプリプロセッサのような置換ではなく、バインディング( let )としてtypeを見る方が便利だと思います。 そのように見ると、 type Foo = impl Traitはまさにそのように見えることを意味します。

初心者はimpl Traitを実存的なタイプと普遍的なタイプではなく、「 impl sa Trait . If they want to know more, they can read the implTrait`のドキュメントだと考える可能性は低いと思います。構文を変更すると、既存の機能との接続が失われ、あまりメリットがありません。_誤解を招く可能性のある構文を別の構文に置き換えるだけです。_

type Foo = _に関しては、完全に無関係な意味で_をオーバーロードします。 また、ドキュメントやGoogleで見つけるのは難しいように思われるかもしれません。

@lnicola constバインディングの代わりに、 let constバインディングを使用することもできます。前者は、参照透過性があります。 let ( fn内で参照透過性がない)を選択することは、特に直感的ではないと思う任意の選択です。 型エイリアスの直感的な見方は、それらがエイリアスであるため、(その単語が使用されていない場合でも)参照透過性であるということだと思います。

また、 typeをCプリプロセッサの代替として見ていません。これは、ジェネリックを回避して尊重する必要があるためです(SFINAEはありません)。 代わりに、すべてのバインディングが純粋であるIdrisやAgdaのような言語でのバインディングとまったく同じように、 typeを考えています。

初心者はimpl Traitを実存的なタイプと普遍的なタイプではなく、「特性を暗示するもの」と考える可能性は低いと思います。

それは私には違いのない区別のように思えます。 専門用語「existential」は使用されていませんが、ユーザーはそれをexistentialタイプ(Rustのコンテキストでは「Barを暗示するあるタイプのFoo」にすぎない)と同じ概念に直感的にリンクしていると思います。

type Foo = _に関しては、完全に無関係な意味で_をオーバーロードします。

どうして? type Foo = _;は、型が予想される他のコンテキストでの_の使用と一致します。
.collect::<Vec<_>>()と書くときと同じように、「実際の型を推測する」という意味です。

また、ドキュメントやGoogleで見つけるのは難しいように思われるかもしれません。

そんなに難しいことではないですか? 「typealiasunderscore」は、うまくいけば、望ましい結果をもたらすはずです...?
「typealiasimpltrait」を検索するのと何ら変わりはないようです。

Googleは特殊文字をインデックスに登録しません。 StackOverflowの質問にアンダースコアが含まれている場合、Googleはアンダースコアという単語を含むクエリに対してアンダースコアを自動的にインデックスに登録しません

@Centril

どうして? type Foo = _;は、型が予想される他のコンテキストでの_の使用と一致します。
.collect ::と書くときと同じように、「実際の型を推測する」という意味です。>()。

ただし、この機能は型を推測してその型エイリアスを提供するのではなく、(モジュールやクレートなどの限定されたスコープの外で)「実際の型」と統合されない実存型を作成します。

Googleは特殊文字をインデックスに登録しません。

これはもはや真実ではありません(おそらく空白に依存しますが..?)。

ただし、この機能は型を推測してその型エイリアスを提供するのではなく、(モジュールやクレートなどの限定されたスコープの外で)「実際の型」と統合されない実存型を作成します。

type Foo = _;の推奨されるセマンティクスは、完全に推論に基づいた、実存的な型エイリアスを持つ代わりの方法です。 それが完全に明確ではなかった場合は、意図をもう少しよく説明する必要がある何かをすぐにフォローアップするつもりです。

@iopq最近の変更に関する@varkorのメモに加えて、他の検索エンジンについても追加したいと思います。公式ドキュメントなどでは、 typeと組み合わせて文字通りの単語「アンダースコア」を明示的に使用することが常に可能です

なんらかの理由で、クエリに_を使用しても良い結果は得られません。 アンダースコアを検索すると、アンダースコアという単語が含まれているものが表示されます。 _を検索すると、アンダースコアが付いているものがすべて表示されるので、関連性があるかどうかさえわかりません。

@Centril

let(fn内で参照透過性がない)を選択することは、特に直感的ではないと思う任意の選択です。 型エイリアスの直感的な見方は、それらがエイリアスであるため、(その単語が使用されていない場合でも)参照透過性であるということだと思います。

申し訳ありませんが、私の直感は完全に後ろ向きであるため、私はまだこれに頭を包むことができません。

たとえば、 type Foo = Bar場合、私の直感は次のようになります。
「 Foo宣言しています。これはBarと同じタイプになります。」

次に、 type Foo = impl Barと書くと、私の直感は次のようになります。
「 Foo宣言しています。これは、 Barを実装する型になります。」

Fooがimpl Bar単なるテキストのエイリアスである場合、それは私には非常に直感的ではありません。 私はこれをテキストとセマンティックのエイリアスとして考えるのが好きです。

したがって、 Fooをimpl Bar置き換えることができる場合、それはテキストのエイリアスであり、私にとってはマクロとメタプログラミングを最も連想させます。 しかし、 Fooが宣言の時点で意味を割り当てられ、その元の意味(文脈上の意味ではない!)で複数の場所で使用できる場合、それはセマンティックエイリアスです。

また、とにかく文脈的存在型の背後にある動機を理解することができません。 トレイトエイリアスがまったく同じことを達成できることを考えると、それらは有用でしょうか?

おそらく、Haskell以外の経歴があるため、参照透過性が直感的ではないと感じています... :)しかし、いずれにせよ、Rustで期待するような動作ではありません。

@ Nemo157 @stjepang

Fooが実際に実存型の型エイリアスである場合

(私の強調)。 「an」を「特定の」と読みました。これは、 fとgが同じ実存型を参照しているため、異なる具象の戻り型をサポートしないことを意味します。

これは、「実存型」という用語の誤用、または少なくとも@varkorの投稿とtype Foo = impl Bar作るように見えることができますFooタイプの別名を∃ T. T: Trait -あなたが代わり場合は∃ T. T: Trait 、使用どこでもFoo 、でも非-テキストでは、各位置で異なる具体的なタイプを取得できます。

この∃ T数量詞(例ではexistential type _0として表されます)のスコープが問題になります。 APITではこのようにタイトです-呼び出し元は∃ T. T: Traitを満たす任意の値を渡すことができます。 しかし、それはRPITではなく、RFC 2071年代にはありませんexistential type宣言し、自分の脱糖であっ例-ない、数量詞は、全機能または全モジュールレベルでは、遠く出ている、とあなたが対処しますどこでも同じT 。

したがって、あいまいさ-すでにimpl Traitがその位置に応じてさまざまな場所に数量詞を配置しているので、 type T = impl Traitどちらを期待する必要がありますか? いくつかの非公式の世論調査、およびRFC 2071スレッドの参加者によるいくつかの事後認識は、それがいずれかの方法で明確ではないことを証明しています。

これが、 impl Traitを存在と関係があるものとして解釈するのではなく、型推論の観点からそのセマンティクスを説明したい理由です。 type T = _は、同じ種類のあいまいさはありません。「 T代わりに_をコピーして貼り付けることはできません」という表面レベルはまだありますが、もはやありません。 「 Tがエイリアスである単一のタイプは、複数の具体的なタイプを意味する可能性があります。」 (不透明/統一しない動作は、 @ varkorがフォローアップについて話していることです。)

参照透過性

タイプエイリアスが現在参照透過性と互換性があるからといって、人々期待しているわけではありません。

例として、 const項目は、(https://github.com/rust-lang/rust/issues/34511#issuecomment-402520768で述べた)透明参考であり、その実際に生じた混乱新旧へユーザー(rust-lang-nursery / rust-clippy#1560)。

したがって、Rustプログラマーにとって、参照透過性は彼らが最初に考えることではないと思います。

@stjepang @kennytm type Foo = impl Trait;型エイリアスが参照透過性で動作することを誰もが期待していると言っているわけではありません。 しかし、このスレッドや他の場所での混乱( @rpjohnstが参照しているもの...)から明らかなように、ユーザーの数は少なくないだろうと思います。 これは問題ですが、おそらく克服できない問題ではありません。 私たちが前進するとき、それは心に留めておくべきことです。

この問題で何をすべきかについての私の現在の考えは、 @ varkorと@rpjohnstに沿って

re:参照透過性

type Foo<T> = (T, T);

type Bar = Foo<impl Copy>;   // not equivalent to (impl Copy, impl Copy)

つまり、すべてのインスタンスで新しい型を生成することでさえ、ジェネリック型エイリアスのコンテキストでは参照透過性ではありません。

@centril type Foo = impl Bar; Foo参照透過性を期待する場合は、手を挙げます。 ただし、 type Foo: Bar = _;場合、参照透過性は期待できません。

呼び出し元(の一部)をモノモーフィングすることにより、 enum impl Traitようなメカニズムなしで、return-position impl Traitを拡張して複数のタイプをサポートすることもできます。 これは、 "強化impl Trait近づくに沿って、それをもたらし、解釈常に実存である" dyn Trait 、と示唆してabstract type使用しない構文impl Traitまったく。

私はこれを内部でここに書きました: https :

新しい実存型を安定させるときの注意点-「実存的」は常に一時的なキーワード(RFCによる)であることが意図されており、(IMO)はひどいものです。 安定させる前に、もっと良いものを考え出さなければなりません。

「実存的」タイプについての話は、物事をクリアしているようには見えません。 impl Traitは、Traitを実装する特定の推論された型を表します。 そのように説明すると、 type Foo = impl Barは明らかに特定の、常に同じタイプであり、実際に役立つ唯一の解釈でもあります。したがって、推測されたコンテキスト以外のコンテキストで使用できます。構造体のように。

この意味で、 impl Traitを_ : Trait書くことも理にかなっています。

@rpjohnst 、

複数のタイプをサポートするためにreturn-position impl Traitを拡張することも可能です。

それはそれを厳密にあまり役に立たimpl型へのエイリアスのポイントは、関数がimpl Fooを返すように定義できることですが、特定の型は他の構造体などでプログラムを介して伝播されます。 これは、コンパイラが適切なenum暗黙的に生成した場合に機能しますが、単形化では機能しません。

@ jan-hudecこれらのアイデアは、Discordに関する議論で出てきました。主に、return-positionとargument-position impl Traitの現在の解釈に一貫性がないという事実に基づいて、いくつかの問題があります。

impl Traitを特定の推論された型を表すようにすることは良いオプションですが、その矛盾を修正するには、Rustが現在持っているものとはimpl Traitの現在の動作。 これはおそらく最も簡単な方法ですが、あなたが言うほど簡単ではありません。

たとえば、 impl Trait 「この新しいタイプの推論を使用して、 Traitを実装する可能な限り多態的なタイプを見つける」ことを意味すると、 type Foo = impl Barはモジュールに関することを暗示し始めます。 abstract typeを推論する方法に関するRFC 2071の規則では、すべての用途が独立して同じタイプを推論する必要があるとされていますが、この多形推論は、少なくともより多くの可能性があることをだけでも、寿命を超える、はるかに多くのもっともらしいアイデアを)得た場合と、その相互作用の周り疑問があるでしょう。

また、「実存的」という言葉を理解しているかどうかや、私たちがどのように教えているかに関係なく、 type Foo = impl Bar構文を実存的のエイリアスとして常に解釈する人もいるという事実もあります。 したがって、推論ベースの解釈でうまくいく場合でも、代替構文を選択することはおそらく依然として良い考えです。

さらに、 _: Trait構文は、実際には、そもそも推論ベースの解釈に関する議論に影響を与えたものですが、私たちが望むことを実行しません。 まず、 _によって暗示される推論はポリモーフィックではないため、他の言語との類似性は良くありません。 次に、 _は実際のタイプが他の場所に表示されることを意味しますが、 impl Traitは実際のタイプを非表示にするように特別に設計されています。

最後に、私が単形化の提案を書いた理由は、引数と戻り位置impl Traitの意味を統一する別の方法を見つけるという観点からimpl Trait 。 はい、それは-> impl Traitが単一の具体的なタイプを保証しなくなったことを意味しますが、とにかくそれを利用する方法は現在ありません。 そして、提案されたソリューションはすべて厄介な回避策です-余分な定型的なabstract typeトリック、 typeofなど。単一タイプの動作に依存したいすべての人に、 abstract type介してその単一タイプにも名前をabstract type構文(それが何であれ)は、間違いなく全体的な利点です。

これらのアイデアはDiscordで議論されており、主にreturn-positionとargument-position impl Traitの現在の解釈に一貫性がないという事実に基づいていくつかの問題があります。

個人的には、この不一致が実際には問題になるとは思いません。 引数の位置と戻りの位置と型の位置について具体的な型が決定される範囲は、かなり直感的に機能するようです。

呼び出し元がリターンタイプを決定する関数があります。 もちろん、そこでimplTraitを使用することはできません。 違いを理解するまでは、暗示するほど直感的ではありません。

個人的には、この不一致が実際には問題になるとは思いません。

それはそう。 これが私に示唆しているのは、矛盾を無視する必要があるということではなく、一貫性があるように設計を再説明する必要があるということです(たとえば、多形型推論として説明することによって)。 このようにして、将来の拡張(RFC 2071など)は混乱になってから物事を防ぐために、新しい、一貫性のある解釈に照らしてチェックすることができます。

@rpjohnst

単一型の動作に依存したいすべての人に、抽象型構文(それが何であれ)を介してその単一型にも名前を付けるように強制することは、間違いなく全体的な利点です。

いくつかのケースでは私はその感情に同意しますが、それはクロージャーやジェネレーターでは機能せず、タイプが何であるかを気にせず、特定の特性を実装することだけを気にする多くの場合には人間工学的ではありませんたとえば、イテレータコンビネータを使用します。

@mikeyhewあなたは私を誤解しています-私はRFC2071 abstract type構文を介して名前を発明することについて話必要があります。

@rpjohnstああ、わかりました、明確にしてくれてありがとう

心配そうにlet x: impl Trait待っています。

let x: impl Traitへの別の投票として、 futures例のいくつかを簡略化します。これが例の例です。現在、 impl Traitを使用する機能を取得するためだけに関数を使用しています。

fn make_sink_async() -> impl Future<Output = Result<
    impl Sink<SinkItem = T, SinkError = E>,
    E,
>> { // ... }

代わりに、これは通常のletバインディングとして記述できます。

let future_sink: impl Future<Output = Result<
    impl Sink<SinkItem = T, SinkError = E>,
    E,
>> = // ...;

必要に応じて、 let x: impl Traitを実装して誰かを指導することができます。 それは不可能なほど難しいことではありませんが、確かに簡単でもありません。 エントリポイント:

https://github.com/rust-lang/rust/blob/master/src/librustc/hir/lowering.rs#L3159のreturntype impl Traitにhttpsのローカルタイプにアクセスする必要があります。 ://github.com/rust-lang/rust/blob/master/src/librustc/hir/lowering.rs#L3159そして、新しく生成された既存のアイテムがローカルと一緒に返されることを確認します。

次に、地元の人のタイプにアクセスするときReturn 、実際に有効にするために、必ずExistentialContextをReturnしてください。

これはすでに私たちを非常に遠ざけるはずです。 ずっとかどうかはわかりませんが、100%リターンポジションの暗黙の特性のようではありませんが、ほとんどの場合、そのように動作するはずです。

@rpjohnst 、

これらのアイデアはDiscordに関する議論で浮かび上がってきましたが、主にreturn-positionとargument-position implTraitの現在の解釈に一貫性がないという事実に基づいていくつかの問題があります。

あなたがあなたの記事で話した範囲に私たちを連れ戻します。 そして、それらは実際には囲んでいる「括弧」に対応していると思います。引数の位置の場合は引数リスト、戻りの位置の場合は関数、エイリアスの場合はエイリアスが定義されているスコープになります。

このスレッドでの議論、元のRFC、および同期の議論に基づいて、 existential type具体的な構文の解決策を提案するRFCを開きました: https :

現在の実存タイプの実装では、現在のすべての戻り位置を表すために使用することはできませんimpl Trait以来、定義impl Traitすべてのジェネリック型引数が、と同じことを行うことが可能であるべきであるとしても、未使用の場合のキャプチャexistential typeですが、未使用の型パラメーターの警告が表示されます:(遊び場)

fn foo<T>(_: T) -> impl ::std::fmt::Display {
    5
}

existential type Bar<T>: ::std::fmt::Display;
fn bar<T>(_: T) -> Bar<T> {
    5
}

タイプパラメータには、値自体が使用されていない場合でも、返されるimpl Trait有効期間を制限する内部有効期間が含まれる可能性があるため、これは問題になる可能性があります。遊び場のBarから<T>を削除してください。上記で、 fooの呼び出しbar機能することを確認します。

現在の実存型の実装を使用して、現在のすべての戻り位置を表すことはできません。

できます、それは非常に不便です。 PhantomDataフィールド+実際のデータフィールドを持つニュータイプを返し、実際のデータフィールドへの転送としてトレイトを実装することができます

@ oli-obk追加のアドバイスをありがとう。 あなたの以前のアドバイスと@cramertjからのいくつかで、私はおそらくすぐにそれを

@fasihrana @ Nemo157上記を参照してください。 たぶん数週間で! :-)

existential typeが型パラメーターを暗黙的にキャプチャし@ Nemo157で言及されている)動作は意図的なものであり、そのまま残ることを誰かが明確にできますか? #42940を解くので気に入っています

わざとこうやって実装しました

@Arnavionはい、これは意図的なものであり、Rustで他のアイテム宣言(ネストされた関数など)が機能する方法と一致します。

existential_typeとnever_type間の相互作用について

たぶん!は、関係する特性に関係なく、あらゆる実存的なタイプを埋めることができるはずです。

existential type Mystery : TraitThatIsHardToEvenStartImplementing;

fn hack_to_make_it_compile() -> Mystery { unimplemented!() }

それとも、存在するタイプを自動的に満たすことができるタイプレベルのunimplemented!()として機能する特別なアンタッチャブルタイプがあるでしょうか?

@viこれは、一般的な「デフォルト以外の非自己メソッドまたは関連する型なしで、型がすべての特性を実装するべきではない」に該当すると思います。 しかし、それがどこで追跡されるかはわかりません。

トレイトメソッドの戻り型のサポートをまもなく拡張する予定はありますか?

existential typeすでにトレイトメソッドで機能します。 Wrt impl Trait 、それはRFCでさえカバーされていますか?

@alexreg fn foo<T>(..) -> impl Bar<T> (およそ-> Self::AnonBar0<T> )のようなものがある場合、GATが匿名の関連付けられたタイプに脱糖できる必要があると思います。

@Centrilはそこでimpl Barに<T>をするつもりimpl Barたか? impl Traitの暗黙的な型キャプチャ動作は、 fn foo<T>(self, t: T) -> impl Bar;ようなものでもGATの同じ必要性を取得することを意味します。

@ Nemo157申し訳ありませんが私はしませんでした。 しかし、あなたの例は問題をさらによく示しています。 ありがとう :)

@alexreg fn fooのようなものがある場合、GATが匿名の関連付けられたタイプに脱糖できる必要があると思います(..)-> implバー(大まかになります-> Self :: AnonBar0)。

ああ、なるほど。 正直なところ、厳密に必要というわけではありませんが、それは確かにそれを実装する1つの方法です。 GATの動きの欠如は、私には少し心配ですが...長い間何も聞いていません。

トリアージ: https : {let,const,static} foo: impl Traitチェックボックスをオンにできると思います。

私はこれまでに書くことができるでしょうか:

trait Foo {
    fn GetABar() -> impl Bar;
}

??

おそらくそうではありません。 しかし、私たちが得るかもしれないようにすべてを準備するための進行中の計画があります

trait Foo {
    type Assoc: Bar;
    fn get_a_bar() -> Assoc;
}

impl Foo for SomeType {
    fn get_a_bar() -> impl Bar {
        SomeThingImplingBar
    }
}

この機能は、次の形式で毎晩試すことができます。

impl Foo for SomeType {
    existential type Assoc;
    fn get_a_bar() -> Assoc {
        SomeThingImplingBar
    }
}

これに関する詳細情報を入手するための良いスタートは、 https://github.com/rust-lang/rfcs/pull/2071 (およびそこからリンクされているすべてのもの)です。

@ oli-obk on rustc 1.32.0-nightly (00e03ee57 2018-11-22) 、このようなimplブロックで機能するには、 existential typeの特性境界も指定する必要があります。 それは期待されていますか?

@jonhooは、必要な特性以上のものを提供できるため、特性を指定できると便利です。

impl Foo for SomeDebuggableType {
    existential type Assoc: Bar + Debug;
    fn get_a_bar() -> Assoc {
        SomeThingImplingBarAndDebug
    }
}

fn use_debuggable_foo<F>(f: F) where F: Foo, F::Assoc: Debug {
    println!("bar is: {:?}", f.get_a_bar())
}

必要な特性は、実存的な関連タイプに暗黙的に追加できるため、それらを拡張するときに必要なのは境界だけですが、個人的には、実装にそれらを配置する必要があるというローカルドキュメントを好みます。

@ Nemo157ああ、すみません、私が意味したのは、現在あなたはそこに限界があるということです。 つまり、これはコンパイルされません。

impl A for B {
    existential type Assoc;
    // ...
}

一方、これは:

impl A for B {
    existential type Assoc: Debug;
    // ...
}

ああ、それで、特性が関連するタイプの境界を必要としない場合でさえ、あなたはまだ存在タイプ(空かもしれない)(遊び場)に境界を与えなければなりません:

trait Foo {
    type Assoc;
    fn foo() -> Self::Assoc;
}

struct Bar;
impl Foo for Bar {
    existential type Assoc: ;
    fn foo() -> Self::Assoc { Bar }
}

これは私にはエッジケースのように思えます。無限の存在型を持つということは、ユーザーに(自動特性以外の)操作を提供しないことを意味します。それで、それは何に使用できるでしょうか?

また、 -> impl Traitで同じことを行う方法はありません。 -> impl ()は構文エラーであり、 -> impl自体はerror: at least one trait must be specifiedます。 実存型の構文がtype Assoc = impl Debug;などになる場合、少なくとも1つの特性がバインドされていないと、関連付けられた型を指定する方法がないようです。

@ Nemo157ええ、私はあなたが上で提案したコードを文字通り試したので気づきました、そしてそれはうまくいきませんでした:p私はそれが特性から境界を推測するだろうと一種の仮定をしました。 例えば:

trait Foo {
    type Assoc: Future<Output = u32>;
}

struct Bar;
impl Foo for Bar {
    existential type Assoc;
}

もう一度Future<Output = u32>を指定する必要がないのは合理的であるように見えましたが、それは機能しません。 existential type Assoc: ; (これも非常に奇妙な構文のようです)もその推論を行わないと思いますか?

trait Foo {
    type Assoc;
    fn foo() -> Self::Assoc;
}

struct Bar;
impl Foo for Bar {
    existential type Assoc: ;
    fn foo() -> Self::Assoc { Bar }
}

これは私にはエッジケースのように思えます。無限の存在型を持つということは、ユーザーに(自動特性以外の)操作を提供しないことを意味します。それで、それは何に使用できるでしょうか?

これらを同じ特性の実装で消費するために使用することはできませんか? このようなもの:

trait Foo {
    type Assoc;
    fn create_constructor() -> Self::Assoc;
    fn consume(marker: Self::Assoc) -> Self;
    fn consume_box(marker: Self::Assoc) -> Box<Foo>;
}

少し工夫が凝らされていますが、役に立つかもしれません。生涯の理由から、実際の構造体の前にいくつかの予備部品を構築する必要がある状況を想像できます。 または、次のようになります。

trait MarkupSystem {
    type Cache;
    fn create_cache() -> Cache;
    fn translate(cache: &mut Self::Cache, input: &str) -> String;
}

どちらの場合も、 existential type Assoc;が役立ちます。

implトレイトに関連付けられたタイプを定義する適切な方法は何ですか?

たとえば、 Actionトレイトがあり、トレイトに関連付けられたタイプの実装が送信可能であることを確認したい場合、次のようにできますか?

pub trait Action {
    type Result;
    fn call(&self) -> Self::Result;
}

impl MyStruct {
    pub fn new(name: String) -> impl Action 
    where 
        Return::Result: Send //This Return should be the `impl Action`
    {
        ActionImplementation::new()
    }
}

これは現在不可能なことですか?

@acycliczebraその構文は-> impl Action<Result = impl Send>だと思います。これは、たとえば、別の匿名のimpl Traitタイプを使用するだけの-> impl Iterator<Item = u32>と同じ構文です。

impl Trait構文を構造体フィールドなどに拡張することについての議論はありますか? たとえば、パブリックインターフェイスの特定のイテレータタイプのラッパーを実装している場合は、次のようになります。

struct Iter<'a> {
    inner: std::collections::hash_map::Iter<'a, i32, i32>,
}

特定の特性の範囲を満たしている限り、実際のタイプをあまり気にしない状況で役立ちます。 この例は単純ですが、過去にネストされた型パラメーターの束を使用して非常に長い型を記述している状況に遭遇しました。これがExactSizeIteratorこと以外は何も気にしないので、本当に不要です。

ただし、IIRCの場合、現時点ではimpl Traitで複数の境界を指定する方法はないと思います。そのため、 Cloneような便利なものを失うことになります。

@AGausmannこのテーマに関する最新の議論は、 https://github.com/rust-lang/rfcs/pull/2515にあります。 これにより、 type Foo = impl Bar; struct Baz { field: Foo } ...と言うことができます。 type Foo = impl Bar;安定させた後、そのための砂糖としてfield: impl Traitを検討したいと思うかもしれません。 それは合理的なマクロフレンドリーな便利な拡張のように感じます。

@Centril 、

field: impl Traitを砂糖と見なしたいと思うかもしれません

これは合理的ではないと思います。 構造体フィールドはまだ具象型でなければならないので、それが関連付けられている関数の戻り値をコンパイラーに通知する必要があります。 それを推測することはできますが、複数の機能がある場合、それがどれであるかを見つけるのはそれほど簡単ではありません。そのような場合、Rustの通常のポリシーは明示的です。

それを推測することはできますが、複数の機能がある場合、それがどれであるかを見つけるのはそれほど簡単ではありません

親型の用途を定義する際の要件をバブルアップします。 その場合、親型を返すのは同じモジュール内のすべての関数になります。 見つけるのはそれほど難しいことではないようです。 ただし、拡張機能を使用する前に、 type Foo = impl Bar;話を解決したいと思います。

現在のexistential type実装にバグが見つかったと思います。


コード

trait Collection {
    type Element;
}
impl<T> Collection for Vec<T> {
    type Element = T;
}

existential type Existential<T>: Collection<Element = T>;

fn return_existential<I>(iter: I) -> Existential<I::Item>
where
    I: IntoIterator,
    I::Item: Collection,
{
    let item = iter.into_iter().next().unwrap();
    vec![item]
}


エラー

error: type parameter `I` is part of concrete type but not used in parameter list for existential type
  --> src/lib.rs:16:1
   |
16 | / {
17 | |     let item = iter.into_iter().next().unwrap();
18 | |     vec![item]
19 | | }
   | |_^

error: defining existential type use does not fully define existential type
  --> src/lib.rs:12:1
   |
12 | / fn return_existential<I>(iter: I) -> Existential<I::Item>
13 | | where
14 | |     I: IntoIterator,
15 | |     I::Item: Collection,
...  |
18 | |     vec![item]
19 | | }
   | |_^

error: could not find defining uses
  --> src/lib.rs:10:1
   |
10 | existential type Existential<T>: Collection<Element = T>;
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

遊び場

これはstackoverflowでも見つけることができます。

このケースをすぐにサポートできるかどうかは100%わかりませんが、できることは、2つの汎用パラメーターを持つように関数を書き直すことです。

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=b4e53972e35af8fb40ffa9a735c6f6b1

fn return_existential<I, J>(iter: I) -> Existential<J>
where
    I: IntoIterator<Item = J>,
{
    let item = iter.into_iter().next().unwrap();
    vec![item]
}

ありがとう!
うん、これは私がstackoverflowの投稿に投稿したようにしたことです:

fn return_existential<I, T>(iter: I) -> Existential<T>
where
    I: IntoIterator<Item = T>,
    I::Item: Collection,
{
    let item = iter.into_iter().next().unwrap();
    vec![item]
}

特性のコンテキスト内でimpl Traitを利用できるようにする計画はありますか?
関連する型としてだけでなく、メソッドの戻り値としても。

トレイトのimplトレイトは、ここで追跡されているものとは別の機能であり、現在RFCはありません。 この分野にはかなり長い設計の歴史があり、2071(既存のタイプ)の実装が安定するまで、さらなる反復は延期されます。これは、実装の問題と未解決の構文(別のRFCがあります)でブロックされます。

@cramertj構文はほぼ解決されています。 現在、主なブロッカーはGATだと思います。

@alexreg : https :

@varkorええ、私はただ楽観的です。彼らはすぐにそのRFCで光を見るでしょう。 ;-)

次のようなことが可能でしょうか?

#![feature(existential_type)]

trait MyTrait {}

existential type Interface: MyTrait;

struct MyStruct {}
impl MyTrait for MyStruct {}

fn with<F, U>(cb: F) -> U
where
    F: FnOnce(&mut Interface) -> U
{
    let mut s = MyStruct {};
    cb(&mut s)
}

hint関数を使用してInterface具体的なタイプを指定する場合にのみ、これを実行できます。

#![feature(existential_type)]

trait MyTrait {}

existential type Interface: MyTrait;

struct MyStruct {}
impl MyTrait for MyStruct {}

fn with<F, U>(cb: F) -> U
where
    F: FnOnce(&mut Interface) -> U
{

    fn hint(x: &mut MyStruct) -> &mut Interface { x }

    let mut s = MyStruct {};
    cb(hint(&mut s))
}

コールバックが引数の型を選択できるとしたら、どのように記述しますか? 実際にはnvmですが、通常のジェネリックを使用して解決できると思います。

@CryZeあなたが探しているものはimpl Traitとは無関係です。 私が知っていることはすべて、 https://github.com/rust-lang/rfcs/issues/2413を参照して

それは潜在的にそのように見えるでしょう:

trait MyTrait {}

struct MyStruct {}
impl MyTrait for MyStruct {}

fn with<F, U>(cb: F) -> U
where
    F: for<I: Interface> FnOnce(&mut I) -> U
{
    let mut s = MyStruct {};
    cb(hint(&mut s))
}

@KrishnaSannasiああ、面白い。 ありがとう!

これは機能するはずですか?

#![feature(existential_type)]

trait MyTrait {
    type AssocType: Send;
    fn ret(&self) -> Self::AssocType;
}

impl MyTrait for () {
    existential type AssocType: Send;
    fn ret(&self) -> Self::AssocType {
        ()
    }
}

impl<'a> MyTrait for &'a () {
    existential type AssocType: Send;
    fn ret(&self) -> Self::AssocType {
        ()
    }
}

trait MyLifetimeTrait<'a> {
    type AssocType: Send + 'a;
    fn ret(&self) -> Self::AssocType;
}

impl<'a> MyLifetimeTrait<'a> for &'a () {
    existential type AssocType: Send + 'a;
    fn ret(&self) -> Self::AssocType {
        *self
    }
}

existential_type機能のために、言語でexistentialキーワードを保持する必要がありますか?

@jethrogbはい。 現在そうではないという事実はバグです。

@cramertjわかりました。 そのために別の問題を提出する必要がありますか、それともここに私の投稿で十分ですか?

問題を提出するのは素晴らしいことです、ありがとう! :)

existential_type機能のために、言語でexistentialキーワードを保持する必要がありますか?

type-alias-impl-trait機能が実装されたとき(つまり、lintに入れられたとき)、これをすぐに非推奨にし、最終的に構文から削除することを意図していると思います。

誰かがおそらく明確にすることができます。

impl Traitより一般的に追跡するメタイシューを支持してこれを閉じる: https :

implトレイトの使用方法に関する良い例は1つもありません、非常に悲しい

このページは役に立ちましたか?
0 / 5 - 0 評価