Rust: RFC 1892の追跡の問題、「新しいMaybeUninitタイプを優先して、初期化されていないものを非推奨にする」

作成日 2018ĺš´08月19日  Âˇ  382コメント  Âˇ  ソース: rust-lang/rust

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

これは、RFC「新しいMaybeUninitタイプを優先してuninitializedを非推奨にする」(rust-lang / rfcs#1892)の追跡の問題です。

手順:

  • [x] RFCを実装します(cc @ rust-lang / libs)
  • [x]ドキュメントを調整します(https://github.com/rust-lang/rust/pull/60445内)
  • [x]安定化PRhttps://github.com/rust-lang/rust/pull/60445内)

未解決の質問:

  • &mut Tを返す安全なセッターが必要ですか?
  • MaybeUninit名前を変更する必要がありますか?
  • into_inner名前を変更する必要がありますか?
  • MaybeUninit<T>はCopyに対してT: Copyますか?
  • データが初期化される前に、 get_refとget_mut呼び出しを許可する必要がありますか(ただし、返された参照からの読み取りは許可しません)? (別名:「初期化されていないデータへの参照はinsta-UBですか、それとも読み取り時にUBのみですか?」) into_innerように名前を変更する必要がありますか?
  • mem::uninitializedが現在行っているように、 Tが無人の場合、 into_inner (またはそれが呼び出されることになったもの)をパニックにすることはできますか? (完了)
  • mem::zeroed非推奨にしたく
B-RFC-approved C-tracking-issue E-mentor T-lang T-libs

最も参考になるコメント

mem::zeroed()は、C関数を呼び出す前にmemset(&x, 0, sizeof(x))値をゼロにすることが期待される特定のFFIの場合に役立ちます。 これは、非推奨にしないための十分な理由だと思います。

全てのコメント382件

cc @RalfJung

[] RFCを実装する

RFCの実装を手伝うことができます。

素晴らしい、私はレビューを手伝うことができます:)

RFCのこの部分について説明をお願いします。

空の型で初期化されていない呼び出しを実行すると、ランタイムパニックが発生し、非推奨メッセージも出力されます。

唯一必要がありますmem::uninitialized::<!>()パニック? または、これは空の型( (!, u8) )を含む構造体(およびおそらく列挙型)もカバーする必要がありますか?

AFAIKは、 !に対してのみ本当に有害なコード生成を行います。 mem::uninitialized他のほとんどの使用法も同様に正しくありませんが、コンパイラーがそれらを悪用することはありません。

だから私は!だけでなく、 mem::zeroedでもやります。 (RFCにzeroedを追加したときに、その部分を修正するのを忘れたようです。)

これを作成することから始めることができます:
https://github.com/rust-lang/rust/blob/8928de74394f320d1109da6731b12638a2167945/src/librustc_codegen_llvm/intrinsic.rs#L184 -L198

fn_ty.ret.layout.abiがAbi::Uninhabitedであるかどうかを確認発行します。例: //github.com/rust-lang/rust/blob/8928de74394f320d1109da6731b12638a2167945/src/librustc_codegen_llvm/mir/オペランド.rs#L400 -L403

トラップ(つまりintrinsics::abort )の動作を確認したら、パニックを引き起こす良い方法があるかどうかを確認できます。 巻き戻しのために注意が必要です。ここで特殊なケースにする必要があります: https :

実際にパニックにするには、次のようなものが必要です: https :
( EvalErrorKind::BoundsCheckアームは無視できます)

@eddybポインタをありがとう。


私は現在、(いくつかの)非推奨の警告を修正していて、(非常に) sed -i s/mem::uninitialized()/mem::MaybeUninit::uninitialized().into_inner()/g実行したいと思っていますが、それは要点を見逃していると思います...または、値が具体的であることがわかっている場合は問題ありません(コピー)タイプ? 例: let x: [u8; 1024] = mem::uninitialized(); 。

ええ、それは正確に要点を見逃します。^^

少なくとも今のところ、すべての非ユニオンタイプに対してmem::MaybeUninit::uninitialized().into_inner() UBを検討したいと思います。 Copyは確かに十分ではないことに注意してください。 boolと&'static i32はどちらもCopyあり、スニペットはそれらのinsta-UBであることが意図されています。 「すべてのビットパターンが問題ないタイプ」(基本的に整数タイプ)の例外が必要な場合がありますが、 undefは通常のビットパターンではないため、このような例外を作成することには反対です。 これが、RFCがinto_inner呼び出す前に完全に初期化する必要があると言っている理由です。

また、 get_mutについても述べていますが、RFCの議論は、ここでの制限を緩和することを望む人々によって提起されました。 それは私が一緒に暮らすことができるオプションです。 ただし、 into_innerはありません。

uninitializedこれらすべての使用法をより注意深く検討する必要があるのではないかと思います。実際、これはRFCの意図の1つでした。 ここでは、より広いエコシステムにもっと注意を払ってもらいたいと思います。誰もがすぐにinto_inner使用するだけなら、RFCは無価値でした。

ここでは、より広いエコシステムにもっと注意を払ってもらいたいと思います。誰もがすぐにinto_inner使用するだけなら、RFCは無価値でした。

これは私に考えを与えます...多分私達はこの種のコードのためにlint(グループ: "正しさ")をすべきでしょうか? cc @ oli-obk

現在、(いくつかの)非推奨の警告を修正しています

推奨される交換品が少なくともStableで利用可能になった場合にのみ、これらの警告とともにNightlyを出荷する必要があります。 https://github.com/rust-lang/rust/pull/52994#issuecomment-411413493で同様の議論を参照して

@RalfJung

「すべてのビットパターンに問題がないタイプ」(基本的に整数タイプ)の例外が必要になる場合があります。

あなたは以前にこれについての議論に参加しましたが、もっと広く回覧するためにここに投稿します:これはすでにフクシアで多くの既存のユースケースを持っているものであり、これには特徴があります( FromBytes )およびこれらのタイプの派生マクロ。 これらを標準ライブラリ(cc @gnzlbg @joshlf)に追加するための内部Pre-RFCもありました。

undefは通常のビットパターンではないため、このような例外を作成することには反対です。

ええ、これはmem::zeroed()がmem::uninitialized()と大幅に異なる側面です。

@cramertj

あなたは以前にこれについての議論に参加しましたが、もっと広く回覧するためにここに投稿します:これはすでにフクシアで多くの既存のユースケースを持っているものであり、これの特徴(FromBytes)と派生マクロがありますこれらのタイプの場合。 これらを標準ライブラリに追加するための内部Pre-RFCもありました(cc @gnzlbg @joshlf)。

これらの議論は、タイプ間で安全なmemcpyを許可する方法に関するものでしたが、コピーされるメモリが初期化されるかどうかとほぼ直交していると思います。初期化されていないメモリを挿入すると、初期化されていないメモリが出力されます。

また、安全なRustで、初期化されていないメモリの形式であるパディングバイトの読み取りを許可することは、議論されたどのアプローチにとっても不健全であるというコンセンサスもありました。 つまり、初期化されたメモリを挿入した場合、初期化されていないメモリを取り出すことはできません。

IIRC、初期化されていないメモリを入れたり、初期化されたメモリを取り出したりするアプローチを提案したり議論したりした人は誰もいなかったので、これらの議論がこれとどう関係しているのかについては説明しません。 私にとって、それらは完全に直交しています。

ポイントをもう少し家に帰すために、LLVMは初期化されていないデータをPoisonとして定義します。これは、「任意ですが有効なビットパターン」とは異なります。 Poison値に基づいて分岐するか、それを使用してアドレスを計算し、それを逆参照するのはUBです。 そのため、残念ながら、「すべてのビットパターンに問題がないタイプ」は、個別に初期化せずに使用するとUBになるため、まだ安全に構築できません。

そうです、すみません、私が何を意味するのかを明確にすべきでした。 「すべてのビットパターンが問題ないタイプ」は、他の理由ですでに定義したいものだと言いたかったのです。 @RalfJungが上で言ったように、

undefは通常のビットパターンではないため、このような例外を作成することには反対です。

どうやら私には読めないので、読める人がいることを神に感謝します...

そうです、私が言いたかったのは、初期化されたすべてのビットパターンが問題ないタイプがあることです。すべてのi* u*タイプとf*だと思います。

未解決の質問とは、これらのタイプのどれが初期化されないことが許可されているか、つまり毒です。 私自身の好みの答えは「決して」ではありません。

また、安全なRustで、初期化されていないメモリの形式であるパディングバイトの読み取りを許可することは、議論されたどのアプローチにとっても不健全であるというコンセンサスもありました。 つまり、初期化されたメモリを挿入した場合、初期化されていないメモリを取り出すことはできません。

パディングバイトをMaybeUninit<u8>として読み取ることは問題ありません。

また、安全なRustで、初期化されていないメモリの形式であるパディングバイトの読み取りを許可することは、議論されたどのアプローチにとっても不健全であるというコンセンサスもありました。 つまり、初期化されたメモリを挿入した場合、初期化されていないメモリを取り出すことはできません。

パディングバイトをMaybeUninitとして読み取る大丈夫なはずです。

一言で言えば議論は、形質を与える程度であったCompatible<T>安全な方法で、 fn safe_transmute(self) -> Tその"再解釈" / "memcpys"のビットselfにT 。 このメソッドの保証は、 selfが適切に初期化されている場合、結果のTも適切に初期化されることです。 コンパイラが推移的な実装を自動的に入力することが提案されました。たとえば、 impl Compatible<V> for Uとimpl Compatible<W> for Vがある場合は、 impl Compatible<W> for U (提供されたため)。手動で、またはコンパイラが自動生成します-これを実装する方法は完全に手作業で行われました)。

トレイトを実装するにはunsafeである必要があることが提案されました。 Selfにフィールドがあるパディングバイトを持つT実装する場合、少なくともすべてが正常です。 Tを使おうとすると、初期化されていないメモリの内容に応じてプログラムの動作が終了するまで。

これがMaybeUninit<u8>と何の関係があるのか​​わかりませんが、詳しく説明していただけますか?

私が想像できる唯一のことは、ブランケットimplを追加できることです: unsafe impl<T> Compatible<[MaybeUninit<u8>; size_of::<T>()]> for T { ... }任意のタイプをそのサイズの[MaybeUninit<u8>; N]に変換することは、すべてのタイプに対して安全であるためです。 MaybeUninitが和集合であり、 [MaybeUninit<u8>; N]を使用する人は誰でも、配列の特定の要素が初期化されているかどうかわからないので、そのようなimplがどれほど役立つかわかりません。 。

@gnzlbg当時、あなたはFromBits<T> for [u8]について話していました。 代わりに[MaybeUninit<u8>]を使用する必要があると私は言います。

私はこの提案についてRustConfの@nikomatsakisと話し合い、彼は私にRFCを進めるように勧めました。 数週間でやろうと思っていたのですが、興味があれば今週末にやってみようと思います。 それはこの議論に役立ちますか?

@joshlfどの提案について話しているのですか?

@RalfJung

@gnzlbg当時あなたはFromBitsについて話していました[u8]の場合。 それは私たちが使用しなければならないと私が言うところです[MaybeUninit]代わりに。

ガッチャ、ここで完全に同意します。 私たちもそれをやりたかったことを完全に忘れていました😆

@joshlfどの提案について話しているのですか?

FromBits / IntoBits提案。 TLDR: T: FromBits<U>手段その有効な任意のビットパターンU有効に相当T 。 U: IntoBits<T>は同じことを意味します。 コンパイラーは、特定のルールが与えられた場合、タイプのすべてのペアについて両方を自動的に推測します。これにより、現在unsafe必要とする多くの楽しい機能が解き放たれます。 しばらく前に書いたこのRFCのドラフトがここにありますが、その大部分を変更するつもりなので、そのテキストを大まかなガイド以上のものと見なさないでください。

@joshlfこのような特性のペアは、この議論の一部であるよりも、この議論の上に構築されると思います。 AFAIKには、妥当性に関して2つの未解決の質問があります。

  • それは参照の下で繰り返されますか? より多くの例を見ると、私はますますそうすべきではないと強く思います。 したがって、それに応じてMaybeUninit::get_mutドキュメントを適応させる必要があります(初期化を完了する前にそれを使用するのは実際にはUBではありませんが、初期化を完了する前に逆参照するのはUBです)。 しかし、私たちは最初にその有効性を決定する必要があり、そのための適切な場所が何であるかはわかりません。 おそらく専用のRFC?
  • u8 (および他の整数型、浮動小数点、生のポインター)を初期化する必要がありますか?つまり、 MaybeUinit<u8>::uninitialized().into_inner() insta-UBですか? 私はそう思いますが、ほとんどの場合、 poison / undefを許可する場所を最小限に抑えたいという直感に基づいています。 ただし、このパターンの使用法がたくさんある場合は、そうでなければ説得される可能性があります(そして、これを決定するためにミリを使用したいと思います)。

それは参照の下で繰り返されますか?

@RalfJungは、「参照の下で繰り返し」の意味の例を示すことができますか?

u8(および他の整数型、浮動小数点、生ポインター)を初期化する必要がありますか?つまり、MaybeUinitです:: uninitialized()。into_inner()insta-UB?

インスタントUBでない場合はどうなりますか? その値で何ができますか? それに合わせてもいいですか? もしそうなら、プログラムの振る舞いは決定論的ですか?

UBを導入せずに値を一致させることができない場合は、 mem::uninitializedを再発明したように感じます。 値を一致させることができ、すべてのアーキテクチャ、オプトレベルなどで常に同じブランチが使用される場合、 mem::zeroedを再発明しました( MaybeUninit利用しているようなものです)。

u8 (および他の整数型、浮動小数点、生のポインター)を初期化する必要がありますか?つまり、 MaybeUinit<u8>::uninitialized().into_inner() insta-UBですか? 私はそう思いますが、ほとんどの場合、 poison / undefを許可する場所を最小限に抑えたいという直感に基づいています。 ただし、このパターンの使用法がたくさんある場合は、そうでなければ説得される可能性があります(そして、これを決定するためにミリを使用したいと思います)。

FWIW、これがUBではないことの2つの利点は、a)LLVMの機能と一致すること、およびb)最適化により柔軟性が増すことです。 また、建設時ではなく、使用時に安全性を定義するという最近の提案とより一致しているようです。

インスタントUBでない場合はどうなりますか? その値で何ができますか? それに合わせてもいいですか? もしそうなら、プログラムの振る舞いは決定論的ですか?

UBを導入せずに値を一致させることができない場合は、 mem::uninitializedを再発明したように感じます。 値を一致させることができ、すべてのアーキテクチャ、オプトレベルなどで常に同じブランチが使用される場合、 mem::zeroedを再発明しました( MaybeUninit利用しているようなものです)。

初期化されていないものに一致させたいのはなぜですか? 初期化されていない値に基づいて分岐またはインデックス付けするUBとして定義すると、LLVMに最適化の余地が増えるため、特に説得力のあるユースケースがない場合は、手を結ぶことは良い考えではないと思います。

初期化されていないものに一致させたいのはなぜですか?

やりたいとは言わなかったのですが、これができないと、 MaybeUinit<u8>::uninitialized().into_inner()とmem::uninitialized()の違いがわからないと言いました。

@RalfJungは、「参照の下で繰り返し」の意味の例を示すことができますか?

基本的に、問題は次のことを許可するかどうかです。

let mut b = MaybeUninit::<bool>::uninitialized();
let bref = b.get_mut(); // insta-UB?

参照が有効なものを指している場合にのみ参照が有効であると判断した場合(これは、「参照の下で繰り返す」という意味です)、このコードはUBです。

インスタントUBでない場合はどうなりますか? その値で何ができますか? それに合わせてもいいですか? もしそうなら、プログラムの振る舞いは決定論的ですか?

初期化されていないu8を検査することはできません。 matchは、名前のバインドと実際の同等性のテストの両方で、多くのことを実行できます。 前者は大丈夫ですが、後者はそうではありません。 ただし、メモリに書き戻すことはできます。

基本的に、これはmiriが現在実装しているものです。

UBを導入せずに値を一致させることができない場合は、mem :: uninitializedを再発明したように感じます。

どうして? mem::uninitializedの最大の問題は、有効な値が何であるかについて制限がある型に関するものでした。 u8はそのような制限がないと判断できたので、 mem::uninitialized()はu8問題ありません
いずれにせよ、初期化されていないu8を安全なコードに渡すことはまだ問題ありませんが、安全でないコードで慎重に使用することは問題ないかもしれません。

無効なデータを指す&mutでも「一致」することはできません。 IOW、上記のbool例は問題ないと思いますが、以下は確かにそうではありません。

let mut b = MaybeUninit::<bool>::uninitialized();
let bref = b.get_mut();
match bref {
  &b => // insta-UB! We have a bad bool in scope.
}

これは、 matchを使用して通常のポインター逆参照を実行しています。

FWIW、これがUBではないことの2つの利点は、a)LLVMの機能と一致すること、およびb)最適化により柔軟性が増すことです。 また、建設時ではなく、使用時に安全性を定義するという最近の提案とより一致しているようです。

これにより、どの最適化が可能になりますか?
LLVMは本質的に型指定されていないコードに対して最適化を行うため、これは問題ではないことに注意してください。 ここでは、MIRの最適化についてのみ説明します。

私は本質的に、明確に使用できるようになるまで、できるだけ許可しないという観点から来ています。 後でいつでもより多くのものを許可できますが、その逆はできません。 とは言うものの、最近、データを古くする可能性のあるバイトスライスのいくつかの良い使用法が登場しました。これは、少なくともu*とi*これを行うには十分な議論になるかもしれません。

参照が有効なものを指している場合にのみ参照が有効であると判断した場合(これは、「参照の下で繰り返す」という意味です)、このコードはUBです。

ガッチャ。

mem :: uninitializedの最大の問題は、有効な値が何であるかについて制限がある型に関するものでした。

mem::uninitializedには、上記で指摘した問題もあります。初期化されていない値への参照を作成すると、未定義の動作になる(またはしない)可能性があります。 それで、次のUBはありますか?

let mut b = MaybeUninit::<u8>::uninitialized().into_inner();
let bref = &mut b; // Insta UB ?

MaybeUninitを導入する理由の1つは、ユニオンを常に初期化(たとえば、ユニット)することでこの問題を回避することであると考えました。これにより、ユニオンを参照し、たとえば設定することで、その内容を変更できます。アクティブフィールドをu8に追加し、UBを導入せずにptr::writeを介して値を指定します。

だから私は少し混乱しているのです。 into_innerが次よりも優れているかどうかわかりません。

let mut b: u8 = uninitialized();
let bref = &mut b; // Insta UB ? 

どちらも私には未定義の振る舞いの時限爆弾のように見えます。

これにより、どの最適化が可能になりますか?
LLVMは本質的に型指定されていないコードに対して最適化を行うため、これは問題ではないことに注意してください。 ここでは、MIRの最適化についてのみ説明します。

未定義のメモリに何らかの価値があると言って、Rustのセマンティクスに従って分岐できる場合、LLVMのバージョンのundefinedに下げることはできません。これは、サウンドが正しくないためです。

私は本質的に、明確に使用できるようになるまで、できるだけ許可しないという観点から来ています。 後でいつでもより多くのものを許可できますが、その逆はできません。

それは公正です。

とは言うものの、最近、データを古くする可能性のあるバイトスライスのいくつかの良い使用法が登場しました。これは、少なくともu*とi*に対してこれを行うには十分な議論になるかもしれません。

これらのユースケースのいずれかに、初期化されていない値を保持するバイトスライスが含まれていますか?

初期化されていないが毒ではない&mut [u8]が価値がある可能性がある場所の1つは、 Read::read -奇妙なReadという理由だけで、バッファをゼロにする必要を回避できるようにしたいと思います。

初期化されていないが毒ではない&mut [u8]が価値がある可能性がある場所の1つは、 Read::read -奇妙なReadという理由だけで、バッファをゼロにする必要を回避できるようにしたいと思います。

なるほど、 MaybeUninitは初期化されているが内容が定義されていないタイプを表し、他のタイプの初期化されていないデータ(たとえば、パディングフィールド)はLLVMポイズンの意味で完全に初期化されていないという考えですか?

一般的にMaybeUninitに適用する必要はないと思います。 理論的には、コンテンツを未定義から定義済みだが任意のコンテンツに「フリーズ」するAPIが存在する可能性があります。

未定義のメモリに何らかの価値があると言って、Rustのセマンティクスに従って分岐できる場合、LLVMのバージョンのundefinedに下げることはできません。これは、サウンドが正しくないためです。

それは決して提案ではありませんでした。 poison分岐するのはUBであり、今後もUBのままです。

問題は、ローカルのu8 poisonを単に「持つ」ことがUBであるかどうかです。

これらのユースケースのいずれかに、初期化されていない値を保持するバイトスライスが含まれていますか?

スライスは参照のようなものなので、初期化されていないデータの&mut [u8]は、書き込まれるだけであれば問題ありません(これが参照の有効性のために採用するソリューションであると想定しています)。

@sfackler

初期化されていないがポイズンではない&mut [u8]が役立つ可能性がある場所の1つは、Read :: readです。奇妙なReadimplがバッファを読み取る可能性があるという理由だけで、バッファをゼロにする必要がないようにしたいと考えています。単に書き込むのではなく。

ええと、 &outがなければ、あなたはimplを知っている場合にのみそれを行うことができます。 問題は、安全なコードがu8 poisonを処理する必要があるかどうかではなく(安全なコードの使用は問題ありません!)、安全でないコードできるかどうかです。こちらです。 (安全性不変量と妥当性不変量の違いについて、今日書きたかったブログ投稿を参照してください...)

遅れているかもしれませんが、 set()メソッドの署名を変更して&mut Tを返すことをお勧めします。 このように、 MaybeUninit動作する完全に安全なコードを書くのは安全です(少なくともいくつかの状況では)。

fn init(dest: &mut MaybeUninit<u8>) -> &mut u8 {
    dest.set(produce_value())
}

これは事実上、 init()が値を初期化するか発散するという静的な保証です。 (何か他のものを返そうとすると、ライフタイムが間違ってしまい、安全なコードでは&'static mut u8は不可能になります。)将来、配置APIの一部として使用される可能性があります。

@Kixunil以前はそうだったし、いいと思う。 同じsetが、何かを返す関数を混乱させていることに気づきました。

@Kixunil

これは事実上、 init()が値を初期化するか発散するという静的な保証です。 (何か他のものを返そうとすると、存続期間が間違ってしまい、安全なコードでは&'static mut u8は不可能になります。)

完全ではありません。 Box::leak入手できます。

最近書いたコードベースで、私は同様のスキームを思いついた。 これは少し複雑ですが、提供された参照が初期化されたことを真に静的に保証します。 の代わりに

fn init(dest: &mut MaybeUninit<u8>) -> &mut u8

私は持っています

fn init<'a>(dest: Uninitialized<'a, u8>) -> DidInit<'a, u8>

秘訣は、 UninitializedとDidInitはどちらもライフタイムパラメータで不変であるため、 DidInitを別のライフタイムパラメータで再利用する方法はありません。たとえば、 'static 。

DidInit DerefとDerefMut意味するので、安全なコードはあなたの例のようにそれを参照として使用できます。 ただし、初期化されたのは実際には元の渡された参照であり、ランダムな他の参照ではないという保証は、安全でないコードに役立ちます。 これは、初期化子を構造的に定義できることを意味します。

struct Foo {
    a: i32,
    b: u8,
}

fn init_foo<'a>(dest: Uninitialized<'a, Foo>,
                init_a: impl for<'x> FnOnce(Uninitialized<'x, i32>) -> DidInit<'x, i32>,
                init_b: impl for<'x> FnOnce(Uninitialized<'x, u8>) -> DidInit<'x, u8>)
                -> &'a mut DidInit<'a, Foo> {
    let ptr: *mut Foo = dest.ptr;
    unsafe {
        init_a(Uninitialized::new(&mut (*ptr).a));
        init_b(Uninitialized::new(&mut (*ptr).b));
        dest.did_init()
    }
}

この関数は、ユーザー提供の初期化コールバックを使用して、各フィールドを順番に初期化することにより、構造体Fooへのポインターを初期化します。 コールバックはDidInit返す必要がありますが、それらの値は気にしません。 それらが存在するという事実で十分です。 すべてのフィールドが初期化されると、 Foo全体が有効であることがわかります。したがって、 Uninitialized<'a, Foo>でdid_init()を呼び出します。これは、にキャストするだけの安全でないメソッドです。対応するDidInitタイプで、 init_fooが返します。

このような関数を作成するプロセスを自動化するマクロもあります。実際のバージョンでは、デストラクタとパニックにもう少し注意が必要です(ただし、改善が必要です)。

とにかく、このようなものを標準ライブラリに実装できるのではないかと思います。

遊び場リンク

(注: DidInit<'a, T>は、実際には&'a mut _DidInitMarker<'a, T>タイプエイリアスであり、 DerefMut存続期間の問題を回避します。)

ちなみに、上記のリンクされたアプローチはデストラクタを無視しますが、わずかに異なるアプローチは、 DidInit<‘a, T> Tのデストラクタの実行を担当させることです。 この場合、エイリアスではなく構造体である必要があります。 また、 ’aすべてではなく、 DidInit自体と同じ長さのTへの参照のみを配布できます(そうでない場合は、破棄後もアクセスを継続できます)。

以前にsetで要求した動作を与えるメソッドを含めるための+1ですが、別の名前で利用できるので問題ありません。

その名前が何であるかについての良いアイデアはありますか? set_and_as_mut ?^^

set_and_borrow_mut ?

insert / insert_mut ? Entryタイプには多少似たor_insertメソッドがあります(ただし、 OccupiedEntryも古い値を返すinsertあるため、まったく似ていません)。

2つの別々の方法を持つ本当に説得力のある理由はありますか? 戻り値を無視するのは簡単なようです。関数は#[inline]としてマークされるので、実際の実行時コストは予想されません。

2つの別々の方法を持つ本当に説得力のある理由はありますか? 戻り値を無視するのは簡単なようです

唯一の理由は、 setが何かを返すのを見るのはかなり驚くべきことだと思います。

何かが足りないかもしれませんが、無効な値を持つことから私たちを救うことができるでしょうか? つまり、

let mut foo: MaybeUninit<T> = MaybeUninit {
    uninit: (),
};
let mut foo_ref = &mut foo as *mut MaybeUninit<T>;

unsafe {
    some_native_function(&mut (*foo_ref).value, val);
}

some_native_functionがno-opであり、実際に値を初期化しない場合はどうなりますか? それはまだUBですか? どのように処理できますか?

@PzixelこれはすべてMaybeUninitのAPIドキュメントでカバーされています。

some_native_functionがNOPの場合、何も起こりません。 その後、後でfoo_ref.value使用する場合(または、パブリックAPIしか使用できないためfoo_ref.as_mut()を使用する場合)、関数はすべてが初期化された後にのみ呼び出される可能性があるため、UBです。

MaybeUninitは無効な値を持つことを防ぎません-可能であれば、それは安全ですが、それは不可能です。 ただし、コンパイラとプログラマの両方が確認できるように、値が無効である可能性があるという情報が型にエンコードされるようになったため、無効な値の操作が簡単になります。

将来発生する可能性のある架空の問題に関して、 @ sfacklerとのIRC会話を文書化したいと

主な質問は、 mem::zeroedがMaybeUninit<NonZeroU8>の現在の実装提案の有効なメモリ内表現であるかどうかです。 私の考えでは、「uninit」状態では、値はパディングのみであり、コンパイラーはこれを任意の目的に使用できます。「value」状態では、 mem::zeroedを除くすべての可能な値が有効です( NonZeroため)

(現在よりも)より高度な列挙型判別パッキングを備えた将来のタイプのレイアウトシステムでは、判別式を「uninit」状態/ゼロ化メモリの「value」状態のパディングに格納する可能性があります。 その架空のシステムでは、 Option<MaybeUninit<NonZeroU8>>のサイズは1ですが、現在は2です。さらに、その架空のシステムでは、 Some(MaybeUninit::uninitialized())はNoneと区別できません。 このようなシステムに移行したら、 MaybeUninit (パブリックAPIではない)の実装を変更することで、おそらくこれを修正できると思います。

この点でNonZeroU8と&'static i32間に違いは見られません。 これらは両方とも「0」が無効なタイプMaybeUninit<T>::zeroed().into_inner()はinsta-UBです。

Option<Union>がレイアウトの最適化を実行できるかどうかは、ユニオンの有効性によって異なります。 これはすべての場合でまだ決定されていませんが、タイプ()バリアントを持つユニオンの場合、任意のビットパターンが有効であるため、レイアウトの最適化は不可能であるという一般的な合意があります。 これはMaybeUninitカバーします。 したがって、 Option<MaybeUninit<NonZeroU8>>サイズが1になることはありません。

タイプ()のバリアントを持つユニオンの場合、任意のビットパターンが有効であるため、レイアウトの最適化は不可能であるという一般的な合意があります。

これは「タイプ()のバリアントを持つユニオン」の特殊なケースですか? この機能の安定化は、Rust ABIのその部分を暗黙的に安定化しますか? struct UnitType;またはstruct NewType(());を含むunion struct NewType(());どうですか? struct Padded (下記)はどうですか? 何についてunion含むstruct Padded ?

#[repr(C, align(4))]
struct Padded {
    a: NonZeroU8,
    b: (),
    c: NonZeroU16
}

私の言い回しはひどく具体的でした。なぜなら、これは文字通り、私たちが一般的に合意していると確信している唯一のことだからです。 :)これをサイズのみに依存させたいと思います(つまり、すべてのZSTがこれを取得します)が、実際にはこのバリアントは必要ないはずであり、ユニオンはデフォルトでレイアウトの最適化を取得しないと思い

現在のコンセンサスを評価するための適切な話し合いがあり、UCGレポの次の議論の1つでさらに多くのことについて合意が得られる可能性があります。それが発生した場合は、ぜひ参加してください。

この機能の安定化は、Rust ABIのその部分を暗黙的に安定化しますか?

ここでは、データレイアウトではなく、有効性の不変条件について説明します(ABIを起動するときに参照すると思います)。 したがって、これによってABIが安定することはありません。 これらは関連していますが明確であり、実際、現在、組合のABIについて継続的な議論が行われています。

これらは関連していますが明確であり、実際、現在、組合のABIについて継続的な議論が行われています。

議論はユニオンのメモリ表現についてのみであり、ユニオンが関数の境界やABIに関連する可能性のあるその他のものをどのように通過するかは含まれていません。 UCGレポの目的はRustのABIを作成することではないと思います。

目的は、Cとの相互運用に十分なものを定義することです。「Rust boolとC boolはABI互換です」などです。

しかし、実際、 repr(Rust)場合、関数呼び出しABIを定義する計画はないと思います。ただし、理想的には、省略だけでなく、結果のドキュメントがどのような形式であっても、明示的なステートメントである必要があります。

Fooが次のように定義されているOption<Foo>レイアウト最適化に反対する議論があるかどうか知りたいです。

union Foo {
   bar: NonZeroUsize,
   baz: &'static str,
}

@Kixunilはhttps://github.com/rust-rfcs/unsafe-code-guidelines/issues/13でそれを上げることができますMaybeUninitとは関係ありません。

初期化せずに静的変数が含まれるセクションを知りたいですか?
「C」では、高レベルのファイルにuint8_t a[100];を書き込むことができ、 .bssセクションにシンボルが配置されることを知っています。 uint8_t a[100] = {};と書くと、シンボルは.dataセクションに配置されメインの前にFLASHからRAMにコピーされます)。

これは、MaybeUninitを使用したRustの小さな例です。

struct A {
    data: MaybeUninit<[u8; 100]>,
    len: usize,
}

impl A {
    pub const fn new() -> Self {
        Self {
            data: MaybeUninit::uninitialized(),
            len: 0,
        }
    }
}

static mut a: MaybeUninit<[u8; 100]> = MaybeUninit::uninitialized();
static mut b: A = A::new();

どのセクションにaとbの記号が含まれますか?

PS私はシンボルマングリングについて知っていますが、この質問には関係ありません。

@ qwerty19106あなたの例では、 aとb両方が.bssます。 LLVMは、変数が入るセクションを選択するときに、 MaybeUninit::uninitialized()ようなundef値をゼロとして扱います。

A::new() lenを1に初期化した場合、 bは.data終了します。 staticにゼロ以外の値が含まれている場合、変数は.dataます。 パディングはゼロ値として扱われます。

これはLLVMが行うことです。 Rustは、 static変数がどのリンカーセクションに入るのかについて〜保証〜を約束しません(*)。LLVMの動作を継承するだけです。

(*) #[link_section]を使用しない限り

おもしろい事実:ある時点で、LLVMはundefをゼロ以外の値と見なしたため、例の変数aは.data終わりました。 #41315を参照してください。

答えてくれてありがとう@japaric 。 とても助かりました。

私は新しい考えを持っています。
mainを呼び出す前に、 .init_arrayセクションを使用して静的mut変数を初期化できます。

これは概念実証です。

#[macro_export]
macro_rules! static_singleton {
    ($name_var: ident, $ty:ty, $name_init_fn: ident, $name_init_var: ident, $init_block: block) => {
        static mut $name_var: MaybeUninit<$ty> = unsafe {MaybeUninit::uninitialized()};

        extern "C" fn $name_init_fn() {
            unsafe {
                $init_block
            }
        }

        #[link_section = ".init_array"]
        #[used]
        static $name_init_var: [extern "C" fn(); 1] = [$name_init_fn];
    };
}

テストコード:

static_singleton!(A, u8, a_init_fn, A_INIT_VAR, {
    let ptr = A.get_mut();
    *ptr = 5;
});

fn main() {
    println!("A inited to {}", unsafe {&A.get_ref()});
}

結果:5に初期化

完全な例:遊び場

未解決の質問:
私はa_init_fnとA_INIT_VARを生成するためにconcat_identsを使用できませんでした。 #1628はまだ使用できる状態になっていないようです。

このテストはあまり役に立ちません。 ただし、複雑な構造体を初期化するための組み込みに役立つ場合があります( .bssに配置されるため、 FLASHを節約できます)。

rustcが.init_arrayセクションを使用しないのはなぜですか? ELF形式の標準化されたセクションです(リンク)。

@ qwerty19106 main()の前の生活は誤った機能と見なされ、Rustのセマンティクスから明示的に招待されなかったためです。

わかりました、それは良いラングデザインです。

しかし、 #[no_std]には、今のところ良い選択肢がありません(たぶん私は悪い検索をしていました)。

spin :: Onceを使用できますが、非常にコストがかかります(すべての参照getでOrdering :: SeqCst )。

組み込みのコンパイル時チェックをしたいのですが。

それは非常に高価です(すべての参照取得でOrdering::SeqCst )。

それは私には正しく聞こえません。 すべての「1回」の抽象化は、アクセス時に緩和され、初期化時に同期されるはずではありませんか? それとも私は何か他のことを考えていますか?
cc @Amanieu @alexcrichton

@ qwerty19106 :

「組み込み」とは、ベアメタルのことですか? .init_arrayは、実際にはELF形式自体の一部ではないことに注意してください¹-それを拡張するSystemVABI²の一部でもありません。 .initです。 LinuxABIが継承するSystemV ABIドラフトアップデートに到達するまで、 .init_arrayは見つかりません。

その結果、ベアメタルで実行している場合、 .init_arrayはユースケースに対して確実に機能しない可能性があります-結局のところ、ダイナミックローダーやlibcのコードによって非ベアメタルに実装されています。 ブートローダーが.init_arrayで参照されているコードの実行に責任を負わない限り、何もしません。

1:28ページの図1-13「特別なセクション」を参照してください。
2:63ページの図4-13「特別なセクション(続き)」を参照してください。

@eddyb Once読み取るときは、少なくともAcquireロードが必要です。 これは、x86の通常の負荷であり、ARMの負荷+フェンスです。

現在の実装ではload(SeqCst)を使用していますが、実際には、これにより、すべてのアーキテクチャでload(Acquire)と同じasmが生成されます。

(これらのディスカッションを別の場所に移動してもよろしいですか?MaybeUninitとmem :: uninitializedとは関係ありません。どちらも、LLVMと同じように動作します-undefを生成します。後でそのundefで何が起こるかについては、ここでは取り上げません。 )

アム13. 2018ĺš´9月夜12時59分20秒MESZ schrieb Amanieu [email protected] :

@eddybを読むときは、少なくともAcquireロードが必要です。
Once 。 これは、x86の通常の負荷であり、ARMの負荷+フェンスです。

現在の実装ではload(SeqCst)を使用していますが、実際にはこれ
すべてのアーキテクチャでload(Acquire)と同じasmを生成します。

-
あなたが言及されたのであなたはこれを受け取っています。
このメールに直接返信するか、GitHubで表示してください。
https://github.com/rust-lang/rust/issues/53491#issuecomment -420825802

MaybeUninitはマスターに着陸し、次の夜になります。 :)

https://github.com/rust-lang/rust/issues/54470は、使用することを提案しているBox<[MaybeUninit<T>]>にRawVec<T> 。 これと、おそらく他の興味深い組み合わせを、核変換の少ないボックスとスライスで有効にするには、標準ライブラリにAPIを追加できるでしょうか。

特に初期化せずに割り当てる場合( Box::new(MaybeUninit::uninitialized())はsize_of::<T>()パディングバイトをコピーすると思いますか?):

impl<T> Box<MaybeUninit<T>> {
    pub fn new_uninit() -> Self {…}
    pub unsafe fn assert_init(s: Self) -> Box<T> { transmute(s) }
}

impl<T> Box<[MaybeUninit<T>]> {
    pub fn new_uninit_slice(len: usize) -> Self {…}
    pub unsafe fn assert_init(s: Self) -> Box<[T]> { transmute(s) }
}

core::slice / std::sliceでは、サブスライスを取得した後に使用できます。

pub unsafe fn assert_init<T>(s: &[MaybeUninit<T>]) -> &[T] { transmute(s) }
pub unsafe fn assert_init_mut<T>(s: &mut [MaybeUninit<T>]) -> &mut [T] { transmute(s) }

Box :: new(MaybeUninit :: uninitialized())はまだsize_of ::をコピーすると思います()パディングバイト

そうすべきではなく、それをテストすることを目的としたcodegenテストがあります。

ビット表現は重要ではないため、パディングバイトをコピーする必要はありません(ビット表現を監視するものはすべてUBです)。

さて、多分Box::new_uninitは不要ですか? ただし、 Box::newはT: Sizedが必要なため、スライスバージョンは異なります。

MaybeUninit::zeroedをconst fnすることを提唱したいと思います。 私がそれに対して持つであろういくつかのFFI関連の使用法があり(例えば、ゼロに初期化されなければならない静的)、他の人がそれが役に立つと思うかもしれないと私は信じています。 zeroed関数を構成するために、ボランティアで時間を割いていただければ幸いです。

@mjbshaw zeroedはmin_const_fnチェックに合格しないことを行うため、そのために#[rustc_const_unstable(feature = "const_maybe_uninit_zeroed")]を使用する必要があります(https://github.com/rust-lang/ rust / issues / 53555)これは、関数が安定していても、 MaybeUninit::zeroedの定数が不安定になることを意味します。

MaybeUninitタイプをより広いエコシステムでより早く利用できるようにするために、これの実装/安定化をいくつかのステップに分割できますか? 手順は次のとおりです。

1)MaybeUninitを追加します
2)mem :: uninitialized / zeroedのすべての使用を変換し、非推奨にします

@scottjmaddox

MaybeUninitを追加

https://doc.rust-lang.org/nightly/core/mem/union.MaybeUninit.html :)

いいね! では、MaybeUninitをできるだけ早く安定させる計画はありますか?

次のステップは、 https://github.com/rust-lang/rust/pull/54668がパフォーマンスをそれほどひどく低下させる理由を理解することです(いくつかのベンチマークで)。 今週はそれを見る時間があまりないのですが、誰か他の人が見てくれたら嬉しいです。 :D

また、これを急ぐべきではないと思います。 初期化されていないデータを処理するための最後のAPIを間違って取得しました。急いで、もう一度失敗しないようにしましょう。 ;)

そうは言っても、私は不必要な遅延を追加したくないので、最終的に古いフットガンを非推奨にすることができます。 :)

ああ、そして私に何か他のことが起こった... https://github.com/rust-lang/rust/pull/54667が着陸すると、古いAPIは実際に最悪のフットガンのいくつかから保護します。 MaybeUninitでもその一部を手に入れることができるのだろうか? それは安定化を妨げていませんが、無人のタイプで呼び出されたときにMaybeUninit::into_innerパニックにする方法を見つけることを試みることができます。 デバッグビルドでは、 T無人のx: &[mut] Tときに、 *xがパニックになることも想像できます。

ステータスの更新:https://github.com/rust-lang/rust/pull/54668を進めるには、ユニオンのレイアウト計算を微調整する人が必要になる可能性があります。 @eddybは喜んで指導しますが、実装を行う人が必要です。 :)

ラッパーから移動して、初​​期化されていない値に置き換えるメソッドが役立つと思います。

pub unsafe fn take(&mut self) -> T

これを提出しませんか?

@shepmasterこれは、既存のinto_innerメソッドと非常によく似ています。 たぶん、ここで重複を避けようとすることができますか?

また、「置換」はここでは間違った図である可能性があります。これによってselfの内容がまったく変わることはありません。 所有権だけが譲渡されるため、初期化されていない状態で構築された場合と実質的に同じ状態になります。

selfの内容を変更する

確かに、実装は基本的にptr::readになりますが、使用法の観点から、有効な値を初期化されていない値に置き換えるものとしてフレーミングすることをお勧めします。

重複を避ける

一方の実装がもう一方を呼び出すことを期待しているので、私は強い異議はありません。 最終的な状態がどうなるかわかりません。

into_innerはあまりにも無邪気な関数名だと思います。 人々は、おそらくドキュメントを注意深く読まなくても、 MaybeUninit::uninitialized().into_inner()ます。 名前をwas_initialized_uncheckedような名前に変更して、データが初期化された後にのみこれを呼び出す必要があることを示すことはできますか?

同じことがおそらくtake当てはまると思います。

少し厄介ですが、 unchecked_into_initializedようなものが機能する可能性がありますか?

または、これらのメソッドを完全に削除し、ドキュメントにx.as_ptr().read()例を示す必要がありますか?

@SimonSapin into_innerはself消費しますが、これは素晴らしいことです。

しかし、 @ shepmasterのtake場合、 as_mut_ptr().read()を実行しても同じことが行われます...もちろん、なぜ可変ポインターを気にする必要があるのでしょうか。

take_uncheckedとinto_inner_uncheckedどうですか?

それは私が推測するバックアップ計画ですが、それがあなたが初期化したに違いないことを示すことができれば私は好むでしょう。

初期化する必要があることを強調することと、それが行うことの説明(unwrap / into_inner /etc。)の両方を1つの名前に入れると、かなり扱いにくくなるので、前者をassert_initializedで実行し、後者を残すのはどうでしょうか。署名によって暗示されますか? assert!()ようにランタイムチェックを暗示することを避けるために、可能なunchecked_assert_initialized 。

assert!()のようなランタイムチェックを暗示することを避けるために、unchecked_assert_initializedの可能性があります。

すでにintrinsics::assume(foo)とassert!(foo)を介して仮定とアサーションを区別しているので、おそらくassume_initialized ?

assumeは不安定なAPIであり、assumeとassertの安定した例はunreachable_unchecked vs unreachableとget_unchecked vs getです。 ですから、 uncheckedが正しい用語だと思います。

foo_uncheckedは、対応するfooがある場合にのみ意味があります。そうでない場合、関数の純粋な性質はunsafeため、「異なる」何かが起こっていることを示します。オン。

この自転車小屋は明らかに間違った色です

この特定のAPIを使用すると、プログラマーは、意図した「これを呼び出すのはUBである」ではなく、「初期化されていないデータはガベージであるため、不注意に処理するとUBが発生する可能性がある」ためであるとプログラマーが想定していることを確認し続けます。初期化されていないデータ、期間」。 uncheckedようなほぼ間違いなく冗長な⚠️がそれを助けるかどうかはわかりませんが、もっと困惑する(=人々に質問したりドキュメントを読んだりする可能性が高い)という側面に誤りがあります非常に慎重に)。

@RalfJung

into_innerはあまりにも無邪気な関数名だと思います。 人々は、おそらくドキュメントを注意深く読まなくても、 MaybeUninit::uninitialized().into_inner()ます。 名前をwas_initialized_uncheckedような名前に変更して、データが初期化された後にのみこれを呼び出す必要があることを示すことはできますか?

私は_本当に_このアイデアが好きです。 セマンティクスについて正しいことを言っていることと、これは潜在的に危険であると強く感じています。

@rkruppe

初期化する必要があることを強調することと、それが行うことの説明(unwrap / into_inner /etc。)の両方を1つの名前に入れると、かなり扱いにくくなるので、前者をassert_initializedで実行し、後者を残すのはどうでしょうか。署名によって暗示されますか? assert!()ようにランタイムチェックを暗示することを避けるために、可能なunchecked_assert_initialized 。

私は危険なものの扱いにくい長いお尻の名前については何の心配もありません。 それがより多くの人々に二度考えさせるなら、 was_initialized_into_inner_uncheckedさえ私にはまったく問題ありません。 安全でないコードを書くことを(理由の範囲内で)非人間的にすることは機能であり、バグではありません;)

大多数の人が何らかの形のオートコンプリートを備えたIDEを使用している可能性が高いため、長い名前はマイナーなロードバンプであることを忘れないでください。

この関数を使用する人間工学は特に気にしませんが、特定のポイントを過ぎると、名前は読むのではなくスキミングされる傾向があると思います。この名前は、何が起こっているのかを理解するために実際に読む必要があります。 さらに、この関数は実際に使用されるのとほぼ同じ頻度で説明/説明されると思います(比較的ニッチで非常に微妙なので)。ソースコードに長い識別子を入力しても問題ありませんが(IDEのおかげで)、チャットシステムのメモリは...あまり良くありません(私はこの点について半分冗談を言っていますが、半分だけです)。

@shepmasterもちろんです。 オートコンプリート付きのIDEも使用しています。 しかし、 unsafeブロック内を含め、内部にuncheckedが含まれる長い名前を使用すると、少なくとも少し休止することになります。

@rkruppe

チャットシステムのメモリからそれらを入力するのは...あまり良くありません(私はこの点について半分冗談を言っていますが、半分だけです)。

私はそのトレードオフを行います。 名前が少し特別な場合は、それをさらに思い出深いものにすることができます。 ;)

いずれか(または同じ意味的含意を含む類似の名前):

  • was_initialized_unchecked
  • was_initialized_into_inner_unchecked
  • is_initialized_unchecked
  • is_initialized_into_inner_unchecked
  • was_init_unchecked
  • was_init_into_inner_unchecked
  • is_init_unchecked
  • is_init_into_inner_unchecked
  • assume_initialized_unchecked
  • assume_init_unchecked

私は元気です。

initialized_into_innerどうですか? またはinitialized_into_inner_unchecked 、あなたがいることを思えばunchecked私がいることを@shepmasterに同意する傾向があるものの、実際には必要であるunchecked同じのいくつかの他の_checked_変種から区別することが必要なだけですランタイムチェックが行われていない機能。

自己借用ジェネレータをptr::drop_in_place(maybe_uninit.as_mut_ptr())複数回使用することになりましたが、これはMaybeUninit固有のメソッドunsafe fn drop_in_place(&mut self)としてうまく機能するようです。

ManuallyDrop::drop前例があります。

foo_uncheckedは、対応するfooがある場合にのみ意味があると思います。そうでない場合、関数が安全でないという性質は、何か「異なる」ことが起こっていることを示します。

安全なバージョンがないことが、安全でないバージョンから警告サインを削除する正当な理由ではないと思います。

安全でないバージョンから警告サインを削除します

少し双曲線なので、 unsafe関数の最後に_uncheckedスタックしないようにする必要があるのはいつですか? 同じことを言う2つの警告があることの意味は何ですか?

それは公正な質問です。 :)しかし、答えは「ほとんどない」と思います。実際、ポインタの安全でない関数としてoffsetあり、それが安全でないことを表すものではないことを残念に思います。 文字通りuncheckedである必要はありませんが、IMOには何かがあるはず.offsetの代わりに.wrapping_offset 、私は私がするつもりはなかったことをコンパイラに約束しました。

安全でないことを決して表現しないポインタの安全でない関数として

これは、この段階での私の面白さを要約しています。

@shepmasterなので、誰かが既存のunsafeブロック内のコードを編集するのは現実的ではないと思います(おそらく大きなブロック、おそらく暗黙的にunsafe持つ大きなunsafe fn内) unsafeブロック)、そして彼らが追加している呼び出しがunsafeことに気づいていませんか?

誰かが既存のunsafeブロック内のコードを編集します[...]そして彼らが追加している呼び出しがunsafeことに気づいていません

申し訳ありませんが、私はこの可能性を却下するつもりはありませんでした、そしてそれは本当のようです。 私の意見では、既存のunsafe修飾子は役に立たないことを示すと、より深刻な障害を示し

下位互換性のある方法で修正できないのは失敗かもしれません。名前に単語を追加することが唯一の可能な解決策ですが、そうではないことを願っています。

多分大きなもの、多分unsafeブロックを暗黙的に持っている大きなunsafe fn中に

数百行の関数がある場合、シャドウイングは明らかに悪い考えであるため、Rustで変数をシャドウイングできる理由を尋ねられました。 私は個人的にそのようなケースを非常に否定しています。なぜなら、そのようなコードはそもそも悪い形式として一般に受け入れられていると信じているからです。

さて、Rustのある側面が安全でないブロックを「必要」よりも大きくすることを強制する場合、それはおそらくより根本的な問題を示しています。


余談ですが、IDE + RLSは、安全でないとマークされた機能を識別し、それらを特別に強調表示できるのではないかと思います。 たとえば、私の編集者はすでにunsafeキーワードを強調表示しています。

さて、Rustのある側面が安全でないブロックを「必要」よりも大きくすることを強制する場合、それはおそらくより根本的な問題を示しています。

https://github.com/rust-lang/rfcs/pull/2585があります;)

余談ですが、IDE + RLSは、安全でないとマークされた機能を識別し、それらを特別に強調表示できるのではないかと思います。

それは素晴らしいことです! ただし、すべての人がIDEでコードを読み取るだけではありません。つまり、レビューは通常IDEで行われるわけではありません。

さて、Rustのある側面が安全でないブロックを「必要」よりも大きくすることを強制する場合、それはおそらくより根本的な問題を示しています。

チェーン内の安全でないメソッドは、より大きな例の1つだと思います。途中でメソッドをlet分割することは、かなり非人間的ですが、そうしない限り、チェーン全体がカバーされます。

完全に「力」ではありませんが、間違いなく「動機付け」です。

l rust-lang / rfcs#2585があります;)

ええ、でも実際にはあなたのケースにも役立たないので、私はそれについて言及しませんでした。 人々はいつでも(コメントで言及されているように)全身の周りにunsafeブロックを追加するだけで、同じ問題にすぐに戻ります。安全でない関数が「忍び込み」を呼び出します。

しかし、誰もがIDEでコードを読むだけではありません

うん、それが私がそれを脇に置いた理由です。 もっとはっきり言っておくべきだったと思います。


私の問題は、事実上、あなたがこれを支持しているということだと思います。

unsafe fn unsafe_real_name_of_function() { ... }
          ^~~~~~ for humans
^~~~~~           for the compiler

これにより、コードを読み取るときに、安全でないすべての関数を明確に確認できます。 繰り返しは私を大いに苛立たせ、何かが最適ではない

これにより、コードを読み取るときに、安全でないすべての関数を明確に確認できます。 繰り返しは私を大いに苛立たせ、何かが最適ではないことを示します。

わかります。 この繰り返しは、コンパイラが2つの目を提供する4つの目の原則を実装していると見なすこともできます。 ;)

@shepmasterこれは少し軌道に乗っていないようですが、IMOの元のポイントは、このメソッドの不変条件が何であるかが明確でないことです。つまり、 unsafeコードが実際にUBでは

「チェックなし」が最善の選択肢ではないことに同意しますが、「不変条件に簡単に違反する」という前例があります。

これにより、 initialized_or_ub沿った命名規則があればいいのにと思います。

これは少し軌道から外れていると思います

私は自分でそう言うところだった。 私は自分の作品を言ったので(そして明らかに誰も私に同意しません)、それを嘘にします。 あなたが望むものは何でも選んでください。

initialized_or_ub沿った命名規則がありました

maybe_uninit(ialized)ような意味ですか? どういうわけか、関連するメソッドのセットに広く適用できるものはありますか? 😇

いいえ、私はunwrap_or_elseような意味です-メソッド名に「不幸なケース」で何が起こるかを入れます。

@eddybねえ、それほど悪くはない... .initialized_or_unsound多分?

一般に、識別子名に型情報を追加することはアンチパターンと見なされます(たとえば、 foo_i32 、 bar_mutex 、 baz_iterator )。

関数に関しては、 unsafeはfnタイプの一部ですが、 _unchecked 、 _unsafe 、 _you_better_know_what_you_are_doing追加するとかなり一般的です。

なぜそうなのかしら?

また、参考までに、 rust-analyzer 、関数がunsafeかどうかを明らかにする問題(https://github.com/rust-analyzer/rust-analyzer/issues/190)があります。 編集者とIDEは、 unsafeブロック内でunsafeを必要とする操作を強調できる必要があります。これには、 unsafe関数の呼び出しだけでなく、次のような識別子が接尾辞として付いているかどうかは関係ありません。たとえば、 _uncheckedかどうか)だけでなく、生のポインタの逆参照なども行います。

間違いなく、 rust-analyzerはまだこれを行うことができません(編集:intellij-Rustの種類の缶:https://github.com/intellij-rust/intellij-rust/issues/3013#issuecomment-440442306)が、これをunsafeブロック内で呼び出すには、 unsafeが必要であることを明確にすることを目的としています。構文の強調表示は、これに何かを付ける代わりに使用できる可能性があります。 つまり、今すぐこれが本当に必要な場合は、この関数の名前を「キーワード」として数分で構文ハイライトに追加し、1日で呼び出すことができます。

@gnzlbg

一般に、識別子名に型情報を追加することはアンチパターンと見なされます(たとえば、 foo_i32 、 bar_mutex 、 baz_iterator )。

確かに、ハンガリアン記法は一般的にアンチパターンと見なされます。 同意します。 しかし、一般的に、これらの議論では安全性は考慮されておらず、UBが提示する危険性を考えると、ここで例外を設ける正当な理由があると思います。

関数に関しては、 unsafeはfnタイプの一部ですが、 _unchecked 、 _unsafe 、 _you_better_know_what_you_are_doing追加するとかなり一般的です。

なぜそうなのかしら?

簡単に言えば、安全ではありません。 安全性が損なわれている場合、冗長性はあなたの友人です。 これは、コードとセーフティクリティカルなハードウェアなどの両方に当てはまります。

また、参考までに、 rust-analyzer 、関数がunsafeかどうかを明らかにする問題( rust-analyzer / rust-analyzer#190 )があります。 編集者とIDEは、 unsafeブロック内でunsafeを必要とする操作を強調できる必要があります。これには、 unsafe関数の呼び出しだけでなく、次のような識別子が接尾辞として付いているかどうかは関係ありません。たとえば、 _uncheckedかどうか)だけでなく、生のポインタの逆参照なども行います。

間違いなく、 rust-analyzerはまだこれを行うことができませんが、 unsafeブロック内でこれを呼び出すにはunsafeが必要であることを明確にすることが目的の場合、構文の強調表示は接尾辞の代わりになります。これは何でも。

これはすべてかなり素晴らしいです。 ただし、 @ RalfJungが指摘したように、 「すべての人がIDEでコードを読み取るだけではありません。つまり、レビューは通常IDEで行われるわけではありません。」 呼び出された関数/操作が安全でないかどうかを示すために、GitHubがUIにrust-analyzerを埋め込む可能性は低いと思います。

トレードオフが醜いこととunsafe誤った(したがって不健全な)使用になりがちなことの間である場合、私たちは常に前者を好むべきだと思います。 プログラマーが一時停止して、「待って、私はこれを正しくやっているのか」と考えるようにすることについては、多くのことが言えます。

たとえば、Mundaneで安全でない暗号化操作を使用する場合は、次のことを行う必要があります。

  • insecureモジュールからインポートします
  • allow(deprecated)を書き込むか、その操作を使用するたびに発行されるコンパイラ警告をそのまま使用します
  • let mut hash = InsecureSha1::default(); hash.insecure_write(bytes); ...ようなコードを書く

それはすべてここでより詳細に文書化

完全に深刻な提案

95%がこのタイプの誤用を心配しており、5%だけが長い名前を心配しているので、タイプの名前をMaybeUninitialized変更することから始めましょう。 余分な7文字はそれだけの価値があります。

主に深刻な提案

  1. 名前をMaybeUninitializedOrUndefinedBehaviorして、エンドユーザーに実際にアピールします。

  2. このタイプを選択してメソッドを持たず、すべてを関連付けられた関数にすることができ、必要に応じて、すべての関数呼び出しのポイントを強化します。

    MaybeUninitializedOrUndefinedBehavior::into_inner(value)
    

愚かな提案

MaybeUninitializedOrUndefinedBehaviorReadTheDocsAllOfThemYesThisMeansYou

ええと...正直なところ、タイプにMaybeUninitializedOrUndefinedBehaviorような長い名前があるのは、私には見当違いのようです。 適切な名前が必要なのは操作.into_inner()です。これは、問題が発生する可能性のあるビットであり、特別な注意が必要なためです。 メソッドがないのは良い考えかもしれません。 MaybeUninit::initialized_or_undefined(foo)はかなり明確に見えます。

IMOは、このように安全でない操作を非人間的なものにするために邪魔をするべきではありません。 人間工学に基づいた名前と、安全でない正しいコードを書く方法が必要です。 過度に長い名前と不明確なユーティリティと変換でそれを乱雑にすると、ユーザーが正しい安全でないコードを書くことを思いとどまらせ、安全でないコードを読み、検証するのが難しくなります。

大多数の人が何らかの形のオートコンプリートを備えたIDEを使用している可能性が高いため、長い名前はマイナーなロードバンプであることを忘れないでください。

RLSがより機能するまでは、少なくとも私には当てはまりません。

私たちのほとんどはそれに同意する

  • よりわかりやすい名前が良い

  • 人間工学に基づいていない名前は悪い

そして問題は、これらが緊張しているときに物事を解決する方法についてです。

それでも、特にinto_innerは、この方法の悪い名前だと思います(派手な用語を使用するために、パレートフロンティアにはありません)。 一般的な規則では、我々が持っているということですinto_innerときFoo<T>正確に一つ含まれているT 、そしてあなたがそれを取得したいです。 しかし、これはMaybeUninit<T>は当てはまりません。 これには、 0個または1個のTが含まれます。

したがって、少なくとも、それほど悪くないオプションは、それをunwrap 、またはおそらくunwrap_uncheckedと呼ぶことです。

また、 from_initializedまたはfrom_initialized_uncheckedは問題ないと思いますが、通常、静的メソッドの名前には「from」が表示されます。

たぶんunwrap_initialized_uncheckedは大丈夫でしょうか?

それを呼び出すtake_initializedとそれ作る&mut selfの代わりにself 。 この名前は、内部値が初期化されることを期待していることを明確にしています。 MaybeUninit 、 unsafeのコンテキストでは、 Option / Result返されないという事実からも、この操作がオフになっていることがわかります。

&mut self取ると、セマンティックにMaybeUninitから移動したかどうかを見失いやすくなるフットガンのように見えます。

別名: intoという名前のメソッドが意味するように、実際には所有権を移動しているので、おそらくinto_initialized_unchecked ?

&mut自己を取ることは、MaybeUninitから意味的に移動したかどうかを見失うことを容​​易にするフットガンのように見えます。

これは要求された方法です

そして、借りたものと消費したものの両方を持つ価値はないようです。

私はtake_initialized 、またはより明示的なバリアントtake_initialized_unchecked好きです。

タイプの名前をMaybeUninitializedに変更することから始めましょう

PRの準備をしている人はいますか?

PRを準備するために?

私はそれを提案したので、私は私の素晴らしいsedスキルを使うことができます;-)

into_innerメソッドを、初期化されていることを前提としていることを強調するものと呼ぶのは改善だと思いますが、 uncheckedの追加は不要で役に立たないと思います。 安全でない関数が安全でないことをユーザーに通知する方法があります。ユーザーが安全でないブロックでラップしないと、コンパイラエラーが生成されます。

編集: take_initializedは良さそうです

assume_initializedどうですか? この:

  • 「証明義務」モデルに接続します
  • 内臓的に「仮定は危険である」に接続します
  • 2つの単語だけが必要です
  • 操作の意味的な意味を説明します
  • かなり自然に読む
  • LLVM assume組み込みのように、誤って想定された場合はUBです

PRの準備をしている人はいますか?

気にしないで。 libsチームは、これは価値がないと判断しました。

T: CopyときにMaybeUninit<T>がCopyはない理由はありますか?

@tommitは、 MaybeUninit<T>がManuallyDrop<T>に依存しているため、プログラマーは、構造体がスコープ外の場合に内部値が削除されることを保証する必要があります。 Copy実装している場合、Rustの新規参入者にとって、構造体自体またはそのコピーのいずれかの内部値T毎回削除することを覚えておくのは難しいかもしれません。 このようにして、私たちが予期していない、より目立たないメモリリークが発生する可能性があります。

@ luojia65 ManuallyDropとMaybeUninitが何をするかに関係なく、 T自体がCopy場合、推論の行が適用されるかどうかはわかりません。

理由はないと思います。 #[derive(Copy)]を追加することを誰も考えていません

これの多分微妙な側面の観察:
私もかかわらず、と信じていますMaybeUninit<T>あるべきCopyときT: Copy 、 MaybeUninit<T>すべきではないCloneときT: CloneとTはCopyはありません。

はい、私たちは間違いなくclone呼び出すことはできません。

私はそのCopy: Clone忘れ続けています...

細かいことを、我々は、実装することができますClone for MaybeUninit<T> where T: Copy返すに基づいて、 *self 。

ここで出てきたすべての質問で問題の説明を更新するために最善を尽くしました。 私が何かを逃したかどうか私に知らせてください!

ManuallyDrop::dropのドキュメントには

この関数は、含まれている値のデストラクタを実行するため、ラップされた値は初期化されていないデータを表します。 初期化されていないデータが実際に使用されないようにするのは、このメソッドのユーザー次第です。

MaybeUninitが処理する「初期化されていない」種類と混同されないように、その表現を改善する方法についての提案はありますか?

私の観点では、ドロップManuallyDrop<T>もはや安全ではありませんTが、それが有効であるT 、少なくともこれまでのレイアウトの最適化は気のように...。

「古い」/「無効」、おそらく? 初期化されます。

FWIW言葉遣いは(少なくとも私には)明確だと思います。
オブジェクトが2回ドロップされないことは、「安全性」の問題です。 小さな文書があったら
「安全性」を定義するUCGでは、おそらくそれをハイパーリンクする必要があります。 あなたは出来る
Tは「有効」であり、ハイパーリンクは「有効」である必要があることを追加します。
これらの定義はまだどこにも書き留められていません...わかりません。 私
ドキュメント全体でそれらを言い換えるべきだとは思わないでください。

初期化されていないものを非推奨にする前に、MaybeUninitを安定させることはできますか?

@RalfJung私はその場所が「から移動した」と言うでしょう。 FWIW std::ptr::readでも同じ種類の用語を使用する必要がありますが、そこでもあまり明確ではありません。

@bluss広く使用されているものを、「より良い」なしで非推奨にするべきではありません。
現在のユーザーの「ソリューション」/「移行パス」。

非推奨の警告は次のようになります:「Xは非推奨です。代わりにYを使用してください」。 もし私達
Yがなく、Xが広く使用されています...それなら、保持することを検討する必要があります
Yになるまで非推奨の警告。

そうでなければ、私たちは本当に奇妙なメッセージを送るでしょう。

@cramertj "invalid"は、妥当性の不変条件を満たしているため、適切な選択ではありません。

「安全性」を定義する小さなドキュメントがUCGにある場合は、おそらくそれをハイパーリンクする必要があります。 Tは「有効」であり、ハイパーリンクは「有効」な定義である必要があることを追加できますが、これらの定義はまだどこにも書き留められていないため...

何かを得たら絶対にやるべきです:D

@RalfJungほとんどの(ほぼすべての?)Rustユーザーのレキシコンに「有効性不変」が含まれているとは思いません-「無効なデータ」を口語的に参照することは許容できると思います( ManuallyDrop<T>はもはや使用できません。 T )。 コンパイラが最適化に使用する特定の表現不変条件を維持する必要があると言っても、無効なデータが少なくなるわけではありません。

ほとんどの(ほぼすべての?)Rustユーザーのレキシコンに「妥当性不変」が含まれているとは思いません。

十分に公平ですが、この用語は(まだ)公式ではありません。 しかし、最終的にはこれの正式な用語を選択する必要があります。そうすれば、そのような衝突を回避する必要があります。 私の投稿では、「有効な」データは「安全」と呼んでいると言えますが、「有効」と呼んでいるものには別の言葉が必要です。

@shepmasterは少し前に書いた

ラッパーから移動して、初​​期化されていない値に置き換えるメソッドが役立つと思います。

pub unsafe fn take(&mut self) -> T

これについての私の最大の懸念は、このような機能を使用すると、コピーされていないデータを誤ってコピーすることが非常に簡単になることだと思います。 それが必要な場合、 maybe_uninit.as_ptr().read()を行うのは本当に悪いですか?

takeようなものをinto_innerようなものに置き換えることをどこかで提案したかもしれません。 それはもう良い考えではないと思います。ほとんどの場合、 into_inner self消費するという追加の制限は実際に役立ちます。

@RalfJung結局、 MaybeUninitすべてのメソッドは安全ではなく、 as_ptr便利なラッパーにすぎません。 しかし、私は期待してtake以来、最も一般的な操作の一つであることがMaybeUninit事実だけでOptionタグが外部で管理されています。 これは多くの場合に役立ちます。たとえば、すべての要素が初期化されていない配列(ハッシュテーブルなど)などです。

https://github.com/rust-lang/rust/pull/57045で、 MaybeUninit 2つの新しい操作を追加することを提案しています:

    /// Get a pointer to the first contained values.
    pub fn first_ptr(this: &[MaybeUninit<T>]) -> *const T {
        this as *const [MaybeUninit<T>] as *const T
    }

    /// Get a mutable pointer to the first contained values.
    pub fn first_mut_ptr(this: &mut [MaybeUninit<T>]) -> *mut T {
        this as *mut [MaybeUninit<T>] as *mut T
    }

動機付けと議論については、そのPRを参照してください。

削除する場合はzeroedそれだけで置き換えられるように思えるMaybeUninit::zeroed().into_inner()同じことを書くための同等の方法となります。 実質的な変更はありません。 uninit値を使用すると、代わりに、初期化されていないすべてのデータがMaybeUninitまたは同等の共用体の値に格納されたままになるという実際的な変更があります。

このため、FFIで広く使用されている関数であるため、 std::mem::zeroedをそのままにしておくことを検討します。 非推奨になると、大きな警告が表示されます。これは、削除されるのとほぼ同じであり、少なくとも非常に煩わしいものです。これにより、 #[allow(deprecated)]数が増え、他のより重要な問題が隠される可能性があります。

unsafeマークされたコードのRustのモデルとガイドラインを明確にするこの演習は非常に便利ですが、新しい言い方を使用して同じ実用的な効果を再キャストするだけのzeroedような変更は避けましょう。 。

私の理解@bluss(間違っている可能性がある)ということであるstd::mem:zeroedのように危険なように均等であるstd::mem::uninitialized 、と同じようにそうUBをもたらすことがあります。 おそらくそれはバイト配列を初期化するために使用されており、 vec![0; N]または[0; N]で初期化する方がよいでしょう。その場合、おそらくrustfixルールを追加して変更を自動化できますか? ただし、バイト配列または整数配列の初期化以外では、 std::mem::zeroed使用するとUBにつながる可能性が高いと理解しています。

@scottjmaddox std::mem:zeroedでUBを呼び出すのは非常に簡単ですが、 std::mem::uninitializedとは異なり、 std::mem:zeroedが完全に有効なタイプがいくつかあります(たとえば、ネイティブタイプ、多くのFFI関連struct sなど)。 多くのunsafe関数と同様に、 zeroed()は軽く使用する必要はありませんが、 uninitialized()ほど問題にはなりません。 安全性の点で2つの間に違いはなく、 MaybeUninitバージョンの方が扱いにくいため、 std::mem:zeroed() MaybeUninit::zeroed().into_inner()代わりに

@mjbshaw

std :: mem :: uninitializedとは異なり、std :: mem:zeroedが完全に有効であるタイプがいくつかあります(たとえば、ネイティブタイプ、

mem::uninitializedが完全に安全であるタイプ(例: unit )もあれば、「ネイティブ」タイプ(例: bool 、 &Tなど)もあります。 。) mem::zeroedが未定義の動作を呼び出す場合。


ここでは、 MaybeUninitが初期化されていないメモリに関するものであるという誤解があるようです(理由はわかります:「初期化されていない」がその名前に含まれています)。

防止しようとしている危険は、_invalid_値にすべてゼロが含まれているか、初期化されていないビットが含まれているか、または他の何か(たとえば、ビットパターンからのboolが含まれているかどうかにかかわらず、_invalid_値の作成によって引き起こされる危険です。 trueまたはfalse )ではなく、実際には問題ではありません- mem::zeroedとmem::uninitializedどちらも_無効な_値を作成するために使用できるため、私の観点からすると、ほぼ同じくらい危険です。

OTOH MaybeUninit::zeroed()とMaybeUninit::uninitialized()は、 unionを返すため、_safe_メソッドです。 MaybeUninit::into_innerはunsafeであり、 MaybeUninit<T>の現在のビットがT _valid_値を表すという前提条件が満たされている場合にのみ、それを呼び出すことは_safe_です。 ビットパターンが_invalid_の場合、動作は未定義です。 すべてゼロ、初期化されていないビット、またはその他のものが含まれているためにビットパターンが無効であるかどうかは、実際には重要ではありません。

@RalfJung MaybeUninitという名前は少し誤解を招くかもしれないと感じ始めています。 解決する問題と回避する危険性をよりよく伝えるために、名前をMaybeInvalidなどに変更する必要があるかもしれません。 編集:私がbikeshedの問題に投稿した@Centrilの提案に従ってください。


編集:FWIW、ゼロ化されたメモリを安全に作成するための人間工学的な方法(たとえば、 MaybeUninitを直接使用せずに)があると便利だと思いますが、 mem::zeroedはありません。 Default似たZeroedトレイトを追加できます。これは、すべてゼロのビットパターンが有効なタイプなどにのみ実装され、同様の効果を実現する方法として使用できます。 mem::zeroedは今はそうですが、落とし穴はありません。

一般に、現在のユーザーがより良いソリューションに移行するためのパスが確立されるまで、機能を非推奨にするべきではないと思います。 MaybeUninitは、私の目にはmem::zeroedよりも優れたソリューションですが、完璧ではないかもしれません(安全ですが、人間工学的ではありません)。したがって、 mem::zeroedを非推奨にしても問題ありません。 MaybeUninit着陸するとすぐに、たとえそれが発生するまでに人間工学に基づいた代替品がない場合でも、 mem::zeroed 。

解決する問題と回避する危険性をより適切に伝えるために、名前をMaybeInvalidなどに変更する必要があるかもしれません。

https://github.com/rust-lang/rust/pull/56138ぎBikeshed

@gnzlbg

いくつかの「ネイティブ」タイプがあります(例: bool

boolがFFIセーフである限り(RFC 954が拒否され、非公式に公式に承認されたにもかかわらず、一般的にはそう考えられています)、 mem::zeroedを使用しても安全です。

、 &Tなど)、 mem::zeroedが未定義の動作を呼び出します。

はい、しかしためUBを持って、これらのタイプmem::zeroedまたのためのUBを持っているMaybeUninit::zeroed().into_inner() (意図的に含まれるように、私は慎重だった.into_inner()私のオリジナルコメントで)。 MaybeUninitは、ユーザーがすぐに.into_inner()呼び出した場合、何も追加しません(これは、 mem::zeroedのみを使用しているため、 mem::zeroedが非推奨になった場合に私や他の多くの人が行うことです。 mem::zeroedタイプの場合は

boolがFFIセーフである限り(RFC 954が拒否され、非公式に公式に受け入れられたにもかかわらず、一般的にはそう考えられています)、mem :: zeroedを使用しても安全です。

私はこれの詳細に立ち入りたくありませんでしたが、 boolは、Cの_Boolと等しくなるように定義されているという意味で、FFIセーフです。 ただし、Cの_Boolのtrueとfalse値は、C標準では定義されていません(ただし、いつかC20で定義される可能性があります)。したがって、 mem::zeroedは有効なbool mem::zeroed作成するかどうかは、技術的に実装定義されています。

はい。ただし、me​​m :: zeroedにUBがあるこれらのタイプには、MaybeUninit :: zeroed()。into_inner()にもUBがあります(元のコメントに意図的に.into_inner()を含めるように注意しました)。 ユーザーがすぐに.into_inner()を呼び出した場合、おそらくUninitは何も追加しません(これは、ゼロセーフの型にのみmem :: zeroedを使用しているため、mem :: zeroedが非推奨になった場合に私や他の多くの人が行うことです) 。

あなたがここでどのポイントを作ろうとしているのか、私にはよくわかりません。 MaybeUninitは、 mem::zeroedはないinto_innerを呼び出すか呼び出さないかのオプションを追加します。これは、未定義の動作を引き起こす可能性がある操作であるため、価値があります(ユニオンを初期化されていない、またはゼロ化されたものとして構築するのは安全です)。

なぜ誰かが盲目的にmem::zeroedをMayeUninit + into_inner翻訳するのでしょうか? これは、 mem::zeroedの非推奨警告を「修正」する適切な方法ではなく、非推奨警告を無音にすることは同じ効果とはるかに低いコストをもたらします。

mem::zeroedからMaybeUninitに移動する適切な方法は、 into_innerを呼び出しても安全かどうかを評価することです。その場合は、そうするだけで、その理由を説明するコメントを書くことができます。安全です。または、 into_inner呼び出しが安全になるまで、 MaybeUninitをunion続けます(その場合は、APIブレークを実行して、多くのコードを変更する必要があります。 Tの代わりにMaybeUninitを返すように変更します。

私はこれの詳細に立ち入りたくありませんでしたが、 boolは、Cの_Boolと等しくなるように定義されているという意味で、FFIセーフです。 ただし、 C's _Bool are not defined in the C standard (although they might be some day, maybe in C20), so whether mem :: zeroed creates a valid bool`のtrueおよびfalse値は、技術的に実装定義されています。 。

接線を継続することをお詫びしますが、C11では、all-bits-set-to-zeroが整数型の値0を表す必要があります(セクション6.2.6.2「整数型」の段落5を参照)( _Bool ) 。 さらに、 trueとfalseは明示的に定義されています(セクション7.18「ブール型と値<stdbool.h> 」を参照)。

あなたがここでどのポイントを作ろうとしているのか、私にはよくわかりません。 MaybeUninitは、 mem::zeroedはないinto_innerを呼び出すか呼び出さないかのオプションを追加します。これは、未定義の動作を引き起こす可能性がある操作であるため、価値があります(ユニオンを初期化されていない、またはゼロ化されたものとして構築するのは安全です)。

MaybeUninitとMaybeUninit::zeroedは値があります。 私たちは両方ともそれに同意します。 MaybeUninit::zeroedを削除することを主張しているわけではありません。 私のポイントは、 std::mem::zeroedも価値があるということです。

mem :: uninitializedが完全に安全であるタイプ(例:unit)もあれば、mem :: zeroedが未定義の動作を呼び出す「ネイティブ」タイプ(例:bool、&Tなど)もあります。

これは赤いニシンです。 zeroedとuninitialized両方がタイプの一部のサブセットに対して有効であるという理由だけで、実際の使用でそれらを比較することはできません。 これらのサブセットのサイズを確認する必要があります。 mem::uninitializedが有効な型の数は非常に少なく(実際、サイズがゼロの型だけですか?)、実際にそれを行うコードを作成する人は誰もいません(たとえば、ZSTの場合は使用するだけです)。型コンストラクター)。 一方、 mem::zeroedが有効なタイプはたくさんあります。 mem::zeroedは、少なくとも次のタイプで有効です(これが正しいことを願っています)。

  • すべての整数型(上記のようにboolを含む)
  • すべての生のポインタタイプ
  • Option<T>ここで、Tは列挙型レイアウトの最適化をトリガーします。 T含まれるもの:

    • NonZeroXXX (すべての整数型)

    • NonNull<U>

    • &U

    • &mut U

    • fnポインタ

    • このリスト内の任意のタイプの任意の配列

    • 任意のstructここで、任意のフィールドはこのリストのタイプです。

  • このリストのタイプのみで構成される任意の配列、 struct 、またはunion 。

はい、 uninitializedとzeroedはどちらも、潜在的に無効な値を処理します。 ただし、プログラマーはこれらのプリミティブを非常に異なる方法で使用します。

mem::uninitializedの一般的なパターンは次のとおりです。

let val = MaybeUninit::uninitialized();
initialize_value(val.as_mut_ptr()); // or val.set
val.into_inner()

初期化されていない値の使用をこのように記述していない場合は、大きな間違いを犯している可能性があります。

今日のmem::zeroedの最も一般的な使用法は、上記のタイプであり、これは完全に有効です。 私は@blussに完全に同意します。どこでも、 MaybeUninit::zeroed().into_inner() mem::zeroed()をMaybeUninit::zeroed().into_inner()置き換えることで、フットガン防止のメリットが見られないということです。

要約すると、 uninitialized一般的な使用法は、無効な値を持つ可能性のあるタイプです。 zeroed一般的な使用法は、ゼロにされた場合に有効なタイプです。

提案されているように、 Zeroed特性または同様のもの(たとえば、 Podですが、 T: ZeroedはT: Pod意味しないことに注意してください)を追加するのは良いことのようです将来ですが、実際に安定したfn zeroed2<T: Zeroed>() -> Tが得られるまで、 fn zeroed<T>() -> T非推奨にしないでください。

@mjbshaw

接線を継続することをお詫びしますが、C11では

確かに! 有効な値が指定されていないのは、C ++のboolです。 私を訂正してくれてありがとう、この保証でUCGにPRを送るつもりです。

@jethrogb

これらのサブセットのサイズを確認する必要があります。 mem::uninitializedが有効な型の数は非常に少なく(実際、サイズがゼロの型だけですか?)、実際にそれを行うコードを作成する人は誰もいません(たとえば、ZSTの場合は使用するだけです)。型コンストラクター)。

ZSTを一種の「プルーフ・オブ・ワーク」または「トークン・フォー・リソース」または単に「プルーフ・ウィットネス」として持つことができるプライバシーを考慮に入れると、すべてのZSTに対してさえ正しくありません。 ささいな例:

mod refl {
    use core::marker::PhantomData;
    use core::mem;

    /// Having an object of type `Id<A, B>` is a proof witness that `A` and `B`
    /// are nominally equal type according to Rust's type system.
    pub struct Id<A, B> {
        witness: PhantomData<(
            // Make sure `A` is Id is invariant wrt. `A`.
            fn(A) -> A,
            // Make sure `B` is Id is invariant wrt. `B`.
            fn(B) -> B,
        )>
    }

    impl<A> Id<A, A> {
        /// The type `A` is always equal to itself.
        /// `REFL` provides a proof of this trivial fact.
        pub const REFL: Self = Id { witness: PhantomData };
    }

    impl<A, B> Id<A, B> {
        /// Casts a value of type `A` to `B`.
        ///
        /// This is safe because the `Id` type is always guaranteed to
        /// only be inhabited by `Id<A, B>` types by construction.
        pub fn cast(self, value: A) -> B {
            unsafe {
                // Transmute the value;
                // This is safe since we know by construction that
                // A == B (including lifetime invariance) always holds.
                let cast_value = mem::transmute_copy(&value);

                // Forget the value;
                // otherwise the destructor of A would be run.
                mem::forget(value);

                cast_value
            }
        }
    }
}

fn main() {
    use core::mem::uninitialized;

    // `Id<?A, ?B>` is a ZST; let's make one out of thin air:
    let prf: refl::Id<u8, String> = unsafe { uninitialized() };

    // Segfault:
    let _ = prf.cast(42u8);
}

@Centrilこれは一種の接線ですが、コードが実際にuninitializedを呼び出すと無効な値が作成されるタイプの例であるかどうかはわかりません。 安全でないコードを使用して、 Idが維持することになっている内部不変条件に違反しています。 これを行うには、 transmute(())や、生のポインタの型キャストなど、さまざまな方法があります。

@jethrogb私の唯一のポイントは、a)言い回しにもっと注意してください、b)有効な値が何であるかについての議論では、プライバシーが十分に理由付けられていないようです。 「内部不変条件に違反する」と「無効な値」は同じもののように思えます。 ここには、「 A != B場合、 Id<A, B>が無人である」という副次的な条件があります。

「内部不変条件に違反する」と「無効な値」は同じもののように思えます。 ここには、「 A != B場合、 Id<A, B>が無人である」という副次的な条件があります。

「ライブラリコードによって課せられた」不変条件は、いくつかの点で「コンパイラによって課された」不変条件とます。Id例には安全性の不変量があり、 mem::zeroedまたはId<A, B>を一般的に合成する他の方法は安全ではありませんが、それはただのUBではありません。 Idには有効性不変条件がないため、 mem::zeroedまたはmem::uninitializedて間違ったId値を作成します。 安全でないコード作成者は確かに両方の種類の不変条件を念頭に置く必要がありますが、この議論が主に有効性に焦点を当てている理由はいくつかあります。

  • 安全不変条件はユーザー定義であり、形式化されることはめったになく、任意に複雑になる可能性があるため、それらについて一般的に推論したり、特定の安全不変条件を維持するのに役立つコンパイラ/言語を使用したりすることはほとんど期待できません。
  • 安全不変条件を破ることが必要になる場合があります(サウンドライブラリ内で)。したがって、 Tの安全不変条件に基づいてmem::zeroed::<T>()を機械的に除外できたとしても、必要ない場合があります。
  • 関連して、壊れた妥当性不変条件の結果は、壊れた安全不変条件よりもいくつかの点で悪いです(すべての地獄がすぐに解き放たれるため、デバッグする機会が少なくなります。また、コンパイラとオプティマイザがすべてあるため、UBから生じる実際の動作が理解しにくいことがよくあります。安全性の不変条件は、同じモジュール/クレート内のコードによってのみ直接利用されます)。

@jethrogbのコメントを読んだ後、 mem::zeroedがMaybeUninit導入で廃止されるべきではないことに同意します。

@jethrogb小さなニット:

このリスト内の任意のタイプの任意の配列
任意のフィールドがこのリストのタイプである任意の構造体。

これが単純なタイプミスなのか意味上の違いなのかはわかりませんが、これら2つの箇条書きを取り除く必要があると思います。たとえばOption<[&u8; 2]> Noneが必ずしもそうであるとは限りません。 Option<[&u8; 2]>は、有効な表現としてビット単位のゼロがあります(たとえば、 None場合の表現として[0, 24601]を使用できます。内部値の1つだけがニッチな表現をとる必要があります-cc @これについて私をチェックするための

@jethrogb

今日のmem :: zeroedの最も一般的な使用法は、上記のタイプであり、これは完全に有効です。

これのソースはありますか?

一方、mem :: zeroedが有効なタイプはたくさんあります。

また、誤って使用されるケースも無限にあります。

mem::zeroed頻繁かつ正確に使用している人にとって、より人間工学的なソリューションが利用可能になるまで非推奨を遅らせることは、非常に魅力的な代替手段であることを理解しています。

一時的な人間工学的コストが発生したとしても、 mem::zeroedの誤った使用の数を減らすか排除するというトレードオフを好みます。 非推奨は、ユーザーが行っていることが未定義の動作を引き起こす可能性があることをユーザーに警告します(特に、初めてそれを使用する新しいユーザー)。代わりに何をすべきかについての適切な解決策があり、警告を実行可能にします。

私はMaybeUninit頻繁に使用し、 mem::zeroedやmem::uninitializedよりも人間工学的ではありませんが、私にとってはそれほど人間工学的ではありません。 MaybeUninitがこのディスカッションの主張の一部のコメントと同じくらい苦痛である場合、安全なmem::zeroed代替案のライブラリおよび/またはRFCがすぐにポップアップします(ここでAFAICTをブロックするものは何もありません)。

または、ユーザーは警告を無視してmem::zeroedを使い続けることができます。これはユーザー次第です。とにかく、 libcoreからmem::zeroedを削除することはできません。

しかし、 mem::zeroed多用している人は、とにかくすべての使用法が正しいかどうかを積極的に検査する必要があります。 特にmem::zeroed多用している人、ジェネリックコードで使用している人、 mem::uninitialized 「怖くない」代替手段として使用している人など。非推奨を遅らせると、ユーザーに自分がしていることを警告するのが遅れるだけです。未定義の動作である可能性があります。

@bluss

ゼロを削除すると、MaybeUninit :: zeroed()。into_inner()にのみ置き換えられるように見えます。これは、同じことを書くための同等の方法になります。 実質的な変更はありません。 uninit値を使用すると、代わりに、初期化されていないすべてのデータが、MaybeUninitタイプまたは同等のunionの値に格納されたままになるという実際的な変更が行われます。

これは整数について話しているときに当てはまりますが、たとえば参照型を見ると、 mem::zeroed()も問題になります。

ただし、 mem::uninitialized::<bool>()が問題であることに気付くよりも、実際にmem::zeroed::<&T>()が問題であることに気付く可能性がはるかに高いことに同意します。 したがって、 mem::zeroed()を保持することは理にかなっています。

ただし、 mem::uninitialized::<u32>()で問題ないと判断する場合があることに注意してください。整数型で初期化されていないビットを許可すると、 mem::uninitialized()はほとんどすべての「POD型」で有効になります。 これを許可するべきではないと思いますが、それでもこの議論をする必要があります。

mem :: uninitializedが有効な型の数は非常に少なく(実際、サイズがゼロの型だけですか?)、実際にそれを行うコードを作成する人は誰もいません(たとえば、ZSTの場合は、型を使用するだけです)。コンストラクタ)。

FWIW、一部のスライスイテレータコードは、型コンストラクタを記述できずに、実際にはジェネリックコードでZSTを作成する必要があります。 そのためにmem::zeroed() / MaybeUninit::zeroed().into_inner()を使用します。

mem::zeroed()は、C関数を呼び出す前にmemset(&x, 0, sizeof(x))値をゼロにすることが期待される特定のFFIの場合に役立ちます。 これは、非推奨にしないための十分な理由だと思います。

@Amanieuそれは不要のようです。 memset一致するRustコンストラクトはwrite_bytesです。

mem :: zeroed()は、特定のFFIの場合に役立ちます

また、前回チェックしたとき、 mem::zeroedは、プライベートフィールドまたはプラットフォーム依存フィールドでlibc構造を初期化する慣用的な方法

@RalfJung問題の完全なコードは通常Type x; memset(&x, 0, sizeof(x));あり、最初の部分にはRustに相当するものがありません。 このパターンにMaybeUninitを使用すると、 memset後でメモリが実際に無効になることはないため、多くのラインノイズ(および最適化なしのcodegenの悪化)が発生します。

MaybeUninitの設計について質問があります: MaybeUninit<T>内に含まれるT単一のフィールドに書き込む方法はありますか?すべてのフィールドが有効/初期化されたタイプになりますか?

次のような構造体があるとします。

// Let us suppose that Foo can in principle be any struct containing arbitrary types
struct Foo {bar: bool, baz: String}

&mut Foo参照を生成し、それに書き込むとUBがトリガーされますか?

main () {
    let uninit_foo = MaybeUninitilized::<Foo>::uninitialized();
    unsafe { *uninit_foo.get_mut().bar = true; }
    unsafe { *uninit_foo.get_mut().baz = "hello world".to_owned(); }
}

参照の代わりに生のポインターを使用すると、この問題を回避できますか?

main () {
    let uninit_foo = MaybeUninitilized::<Foo>::uninitialized();
    unsafe { *uninit_foo.as_mut_pointer().bar = true; }
    unsafe { *uninit_foo.as_mut_pointer().baz = "hello world".to_owned(); }
}

または、UBをトリガーせずにこのパターンを実装できる他の方法はありますか? 直感的には、初期化されていない/無効なメモリを読み取っていない限り、すべてがうまくいくはずですが、このスレッドのコメントのいくつかは私にそれを疑わせます。

この機能の私のユースケースは、一部のフィールドをユーザーが指定する必要がある(そして適切なデフォルトがない)タイプのインプレースビルダーパターンですが、一部のフィールドにはデフォルトがあります値。

MaybeUninit内に含まれるTの単一のフィールドに書き込む方法はありますか?時間の経過とともにすべてのフィールドに書き込み、最終的に有効な/初期化されたタイプになる可能性がありますか?

はい。 使用する

ptr::write(&mut *(uninit.as_mut_ptr()).bar, val1);
ptr::write(&mut *(uninit.as_mut_ptr()).baz, val2);
...

これにはget_mut()を使用しないでください。そのため、 get_mutのドキュメントには、このメソッドを呼び出す前に値を初期化する必要があると記載されています。 将来的には、 https://github.com/rust-rfcs/unsafe-code-guidelines/で議論されているそのルールを緩和する可能性があり

@RalfJung *(uninit.as_mut_ptr()).bar = val1;は、初期化されていない可能性のあるbarに以前の値をドロップするリスクはありませんか? やる必要があると思います

ptr::write(&mut (*uninit.as_mut_ptr()).bar, val1);

@scottjmaddoxああ、そうだね。 Dropを忘れてしまいました。 投稿を更新します。

初期化されていないフィールドへの書き込みのこのバリアントは、 get_mut()よりも未定義の動作をどのように示しますか? ptr::writeへの最初の引数が評価されるコードポイントで、コードは内部フィールドに&mut _を作成しました。これは、そうでない場合は構造体全体への参照と同じように未定義である必要があります。作成した。 コンパイラは、これがすでに初期化された状態にあると想定することを許可されるべきではありませんか?

それは、公開された&mut _中間体を必要としない新しいポインター投影法を必要としないでしょうか?


少し興味深い例:

pub struct A { inner: bool }

pub fn init(mut uninit: MaybeUninit<A>) -> A {
    unsafe {
        let mut previous: [u8; std::mem::size_of::<bool>()] = [0];

        {
            // Doesn't the temorary reference assert inner was in valid state before?
            let inner_ptr: *mut _ = &mut (*uninit.as_mut_ptr()).inner;
            ptr::copy(inner_ptr as *const [u8; 1], (&mut previous) as *mut _, 1);

            // With the assert below, couldn't the compiler drop this?
            std::ptr::write(inner_ptr, true);
        }

        // Assert Inner wasn't false before, so it must have been true already!
        assert!(previous[0] != 0);

        // initialized all fields, good to proceed.
        uninit.into_inner()
    }
}

しかし、コンパイラが&mut _を有効な表現であると想定する場合、 ptr::write完全に捨てられる可能性がありますか? アサーションを通過した場合、コンテンツは0はありませんが、他の有効なブール値はtrue/1です。 したがって、アサーションを通過した場合、これはノーオペレーションであると見なすことができます。 以前は値にアクセスしていなかったので、並べ替えた後、これで終わる可能性がありますか? llvmが今これを悪用しているようには見えませんが、これが保証されるかどうかは非常にわかりません。


代わりに、関数内に独自のMaybeUninitを作成すると、わずかに異なる現実が得られます。 遊び場では、代わりに、アサートがトリガーされないことを前提としていることがわかります。おそらく、 str::ptr::writeがinnerへの唯一の書き込みであると想定しているため、 previousから読み取る前にすでに発生している必要があります。 falseに変更するとどうなるかを確認してください。


この追跡の問題は、この質問に最適な場所ではない可能性があることを理解しています。

@ RalfJung @ scottjmaddoxご回答ありがとうございます。 これらのニュアンスがまさに私が尋ねた理由です。
@HeroicKatoraはい、私はそれについて疑問に思っていました。

おそらく正しい呪文はこれですか?

struct Foo {bar: bool, baz: String}

fn main () {
    let mut uninit_foo = MaybeUninit::<Foo>::uninitialized();
    unsafe { ptr::write_unaligned(&mut ((*uninit_foo.as_mut_ptr()).bar) as *mut bool, true); }
    unsafe { ptr::write_unaligned(&mut ((*uninit_foo.as_mut_ptr()).baz) as *mut String, "".to_string()); }
}

(遊び場)

Reddit(残念ながらもう見つかりません)に関するコメントを読みました。これは、ポインター( &mut foo as *mut T )への参照をすぐにキャストすると、実際にはポインターを作成するだけにコンパイルされることを示唆しています。 しかし、 *uninit_foo.as_mut_ptr()ビットは私を心配させます。 このようにユニタライズされたメモリへのポインタを逆参照しても大丈夫ですか? 私たちは実際には何も読んでいませんが、コンパイラがそれを知っているかどうかは私にはわかりません。

MaybeUninit<T>を超えるジェネリックコードには、 ptr::writeのunalignedバリアントが必要になる可能性があると考えました。これは、すべてのタイプにフィールドが整列されているわけではないためです。

write_unaligned必要ありません。 コンパイラがフィールドの配置を処理します。 また、コンパイラは&mutを*mutに強制変換する必要があると推測できるため、 as *mut boolも必要ありません。 この推測された強制が安全/有効である理由だと思います。 明示的にしてas *mut _実行したい場合は、それでも問題ありません。 ポインタを変数に保存したい場合は、それをポインタに強制変換する必要があります。

@scottjmaddox構造体が#[repr(packed)]あっても、 ptr::writeは安全ですか? ptr::writeは、ポインターを正しく整列させる必要があると言っているので、パックされた表現を処理する必要のあるジェネリックコードを作成する場合は、 ptr::write_unalignedが必要だと思います(正直なところ、わかりませんが)フィールドが適切に配置されているかどうかわからない「 MaybeUninit<T>超えるジェネリックコード」の例を考えることができます)。

@nicoburns

これは、ポインタへの参照(&mutfooを* mut Tとして)をすぐにキャストすると、実際にはポインタを作成するだけにコンパイルされることを示唆しています。

コンパイル対象は、コンパイラがこのコンパイルを実行するために使用できるセマンティクスとは異なります。 IRで何も実行されない場合でも、コンパイラーに追加の仮定をアサートするなどのセマンティック効果があります。 @scottjmaddoxは正しいですが、ここでは操作が実行されていますが、問題の重要な部分は、ref-to-ptr強制の前に独立して発生する可変参照の作成です。 次に、 @ mjbshawは、引数が不明なジェネリック引数である場合にptr::write_unaligned必要とする一般的な安全性について技術的に正しいです。

これをどこで読んだかは覚えていませんが(nomicon? @RalfJungのブログ投稿の1つ?)、生のポインター逆参照、参照、および参照からポインターへの即時変換(いずれかを介して)によるフィールドアクセスはかなり確実です。強制または鋳造)は特別な場合です。

初期化されていないフィールドへの書き込みのこのバリアントは、get_mut()よりも未定義の動作をどのように示しますか? ptr :: writeへの最初の引数が評価されるコードポイントで、コードは内部フィールドに&mut _を作成しました。これは、そうでなければ作成される構造体全体への参照と同じように未定義である必要があります。 コンパイラは、これがすでに初期化された状態にあると想定することを許可されるべきではありませんか?

とても良い質問です! これらの懸念が、 https://github.com/rust-lang/rfcs/pull/2582を開いた理由の1つ&mut作成せず、 *mutます。

@mjbshawTouché 。 はい、構造体がパックされる可能性については正しいと思います。そのため、 ptr::write_unalignedが必要です。 主に錆びた構造物をまだ使用していないため、これまで考えたことはありませんでした。 まだの場合、これはおそらく糸くずの出ない糸くずであるはずです。

編集:関連する糸くずが見当たらないので、問題を送信しました: https :

非推奨のmem::zeroed非推奨にするためにPRを開きました: https :

私はRFCリポジトリで問題を開いて、安全なメモリゼロ化に関する議論をフォークしました。その問題のより良い解決策が得られたら、ある時点でmem::zeroedを非推奨にすることができます: //github.com / rust-lang / rfcs / issues / 2626

const uninitialized 、 as_ptrを安定させることは可能でしょうか?
他のAPIよりもas_mut_ptr先行していますか? これらは私には非常にありそうです
今のように安定します。 さらに、APIの残りの部分は上に構築できます
as_ptrとas_mut_ptr上位にあるため、安定すると次のことが可能になります。
crates.ioにMaybeUninitExtトレイトがあり、安定したAPIを提供します
それは現在、より多くの人々(例えば、安定版のみのユーザー)を許可することについて議論されています
フィードバックをお寄せください。

組み込みでは、グローバルアロケーター(不安定)の代わりに、静的変数を使用します。
たくさん。 MaybeUninitがないと、初期化されていないメモリをに入れる方法はありません。
安定版の静的変数。 これにより、固定容量を配置できなくなります
静的変数のコレクションと実行時の静的変数の初期化
ゼロコスト。 APIのこのサブセットを安定させると、これらのユースケースのブロックが解除されます。

これが組み込みコミュニティにとってどれほど重要であるかを理解するために、
[調査]コミュニティに彼らの問題点とニーズについて尋ねる。 安定化
MaybeUninitは、安定化するために2番目に要求されたものとして出てきました(後ろに
const fn特性の限界あり)そして全体として、数十のうち7位で終了しました
rust-lang / *関連のリクエスト。 WG内でさらに審議した後、私たちはぶつかりました
生態系への影響が予想されるため、全体として3位に優先されます。

(もっと個人的な話ですが、私は組み込み同時実行フレームワークの作成者です
これは、内部でMaybeUninitを使用することでメリットがあります(メモリ使用量は
ユーザーコードを変更せずに、アプリケーションを10〜50%削減できます)。 私
これには夜間のみの貨物機能を提供
毎晩-埋め込まれているだけで、つい最近安定したばかりだと思います
夜間のみの機能を提供すると、ユーザーに送信するメッセージが間違ってしまいます
そのため、このAPIが安定するのを心待ちにしています。)

@japaricそれは確かにinto_innerと友達の周りの命名の議論を避けるでしょう。 しかし、私はまだセマンティックな議論について心配しています。たとえば、 let r = &mut *foo.as_mut_ptr();を実行しているため、有効な参照があると主張している人々についてですが、参照の有効性要件が何であるかはまだわかりません。無効なデータへの参照があるかどうかはまだわかりません-UB。 具体的な例:

let x: MaybeUninit<!> = MaybeUninit::uninitialized();
let r: &! = &*x.as_ptr() // is this UB?

この議論は、UCGWGで最近始まった

私の望みは、初期化されていないデータの適切なストーリーを備えた単一の一貫した「パッケージ」でMaybeUninitを安定させ、人々がこれらのことを少しずつリリースするのではなく、一度だけ再学習する必要があることでした。ピースと多分途中でいくつかのルールを変更する必要があります。 しかし、それは良い考えではないかもしれません。現状を改善するために何かを出すことがより重要ですか?

しかし、いずれにしても、 https://github.com/rust-lang/rfcs/pull/2582を受け入れる前に何も安定させるべきではないと思いないことを確実に人々に伝えることができます。

let x: MaybeUninit<(!, u32)> = MaybeUninit::uninitialized();
let r1: *const ! = &(*x.as_ptr()).1; // immediately coerced to raw ptr, no UB
let r2 = &(*x.as_ptr()).1 as *const !; // immediately cast to raw ptr, no UB

(いつものように、 !はここでは赤いニシンであり、この投稿のすべての例は、代わりにboolを使用した場合、UBに関して同じです。)

私の望みは、初期化されていないデータの適切なストーリーを備えた単一の一貫した「パッケージ」でMaybeUninitを安定させ、人々がこれらのことを1つずつリリースするのではなく、一度だけ再学習する必要があることでした。途中でいくつかのルールを変更します。

この議論は非常に説得力があると思います。

最も差し迫った必要性は、UBなしで初期化されていないメモリを処理する方法について明確なメッセージを伝えることだと思います。 それが現在単に「生のポインターとptr::read_unalignedとptr::write_unaligned 」である場合、それは問題ありませんが、初期化されていないスタック値と構造体/タプルフィールドへの生のポインターを取得するための明確な方法が必要です。 rust-lang / rfcs#2582 (およびいくつかのドキュメント)は当面のニーズを満たしているようですが、 MaybeUninitは満たしていません。

@scottjmaddoxそのRFCはどうですか?ただし、 MaybeUninitないと、初期化されていない(スタック)メモリに適していますか?

@RalfJungそれは以下がUBであるかどうかに依存すると思います:

let x: bool = mem::uninitialized();
ptr::write(&x as *mut bool, false);
assert_eq!(x, false);

私の暗黙の前提は、 rust-lang / rfcs#2582が上記の例を有効にし、明確に定義することでした。 そうではありませんか?

@scottjmaddox

let x: bool = mem::uninitialized();

これはUBです。 参照とは何の関係もありません。

私の暗黙の前提は、rust-lang / rfcs#2582が上記の例を有効にし、明確に定義することでした。

私はこれに完全に驚いています。 そのRFCは参照のみに関するものです。 なぜそれがブール値について何かを変えると思いますか?

@RalfJung

これはUBです。 参照とは何の関係もありません。

mem :: uninitialized()のドキュメントには次のように書かれています。

何もせずに、タイプTの値を生成するふり

ドキュメントにはT*については何も書かれていません。

@kpp何を言おうとしているの? その1行のコードには*も&ありません。

let x: bool = mem::uninitialized();

なぜこの行をUBだと主張するのですか?

boolは常にtrueまたはfalseである必要がありますが、これはそうではありません。 https://github.com/rust-rfcs/unsafe-code-guidelines/blob/master/reference/src/glossary.md#validity-and-safety-invariantも参照して

そのステートメントが動作を定義するための@kpp mem::uninitializedは、_valid_ boolを実体化する必要があります。

現在サポートされているすべてのプラットフォームで、 boolは2つの_valid_値、 true (ビットパターン: 0x1 )とfalse (ビットパターン: 0x0 )しかありません。

ただし、 mem::uninitializedは、すべてのビットの値がuninitializedあるビットパターンを生成します。 このビットパターンは0x0でも0x1でもないため、結果のboolは_無効_であり、動作は未定義です。

動作を定義するには、 boolの定義を変更して、 true 、 false 、またはuninitialized 3つの有効な値をサポートする必要があります。 ただし、T-langとT-compilerはboolがCの_Boolと同一であるとすでにRFCを行っており、その保証を破ることはできないため、これを行うことはできません(これにより、 boolはC FFIで移植可能に使用されます)。

間違いなく、CはRustとまったく同じ有効性の定義を持っていませんが、Cの「トラップ表現」は非常に近いものです。 一言で言えば、未定義の振る舞いを呼び出さずに値がtrueまたはfalseを表さない_Boolを使用してCでできることはあまりありません。

あなたが正しければ、次の安全なコードもUBでなければなりません:

let x: bool;
x = true;

明らかにそうではありません。

正しくない場合は、次の安全なコードもUBである必要があります。

let x: bool;はxをuninitializedビットパターンに初期化しません。 xをまったく初期化しません。 x = true; x初期化します(注:使用する前にx初期化しないと、コンパイルエラーが発生します)。

これは、コンテキストに応じて、 _Bool x; xを_indeterminate_値に初期化するCの動作とは異なります。

いいえ、コンパイラはxが初期化されていないことを認識しています。

問題mem::uninitialized 、それは限り、コンパイラの初期化追跡に関しては、変数を初期化していることです。

let x: bool;は、それ自体ではxを格納するためのスペースを予約することすらなく、名前を予約するだけです。 let x = foo;スペースを予約し、 fooを使用して初期化します。 let x: bool = mem::uninitialized();はx用に1バイトのスペースを予約しますが、初期化されないままにします。これは問題です。

これは、脚で設計されたAPIを撮影するための非常に簡単な方法であるため、mem :: uninitializedと組み込み関数:: uninitの両方で、mem :: uninitializedに特化して文書化する必要があります。コンパイル中にパニックになります。

また、mem :: uninitializedを使用してboolを含む構造体を初期化することもUBであることを意味しますか?

@kpp

また、mem :: uninitializedを使用してboolを含む構造体を初期化することもUBであることを意味しますか?

はい-おそらくお気づきかもしれませんが、 mem::uninitializedすると、足を撃つのは簡単です。正しく使用することはほとんど不可能だと言っても過言ではありません。 そのため、 MaybeUninitを優先して非推奨にしようとしています。これは、使用するのが少し冗長ですが、ユニオンであるため、実際に具体化せずに値を「パーツごとに」初期化できるという利点があります。値自体は_無効_状態です。 値は、 into_inner()呼び出すまでに完全に_有効_である必要があります。

チェックされたおよびチェックされていない(非)初期化に関するノミコンのセクションを読むことに興味があるかもしれません: https ://doc.rust-lang.org/nomicon/checked-uninit.htmlそれらはlet x: bool;初期化の方法をカバーしています安全なRustで動作します。 説明がはっきりしない場合やわからないことがある場合は、記入してください。 また、RFCプロセスをまだ通過していないため、ほとんどの説明は「非規範的」であることに注意してください。 安全でないコードガイドラインWGは、RFC文書を提出し、今年中に現在の動作を保証しようとします。

これは、脚で設計されたAPIを撮影するための非常に簡単な方法であるため、mem :: uninitializedとintrinsics :: uninitの両方で文書化する必要があります。

問題は、現在これを行う正しい方法がないことです。そのため、 MaybeUninit安定させて、これらの関数のドキュメントを太い「使用しないでください」に置き換えることができるように努力しています。


これと同様の問題などの議論、この私たちはできるだけ早く、ドアの外に何かを取得する必要があることを@japaricで、より多くの同意を確認します。 基本的に、これとこのチェックボックスのリストをチェックする必要があります。 次に、いくつかの基本的なパターンを提供するのに十分な量があります。

const uninitialized、as_ptrおよびを安定化することは可能でしょうか
他のAPIよりも前にas_mut_ptr? これらは私には非常にありそうです
今のように安定します。

このために+1。 この機能を安定版で利用できると便利です。 これにより、人々はこの基本的な低レベルのAPIに加えて、さまざまな高レベルの(そして潜在的に安全な)APIを試すことができます。 そして、APIのこの側面はかなり議論の余地がないようです。

さらに、 get_refとget_mutは決して安定せず、完全に削除されることをお勧めします。 通常、参照を操作する方が生のポインターを操作するよりも安全です(したがって、安全でないとマークされていても、 as_ptrおよびas_mut_ptr超えてこれらのメソッドを使用したくなる可能性があります)が、この場合はポインターメソッドはUBを引き起こす可能性があるのに対し、生のポインターメソッドよりも厳密に危険です。

ルールが「初期化されていないメモリへの参照を作成したことがない」の場合、内部で行うヘルパーメソッドを使用するのではなく、明示的に作成することによってのみそのような参照を作成できるようにすることで、このルールに準拠できるようにする必要があると思います。 。

https://github.com/rust-lang/rfcs/pull/2582と仮定すると、(2)はUBであり、(1)にはポインターの参照解除も含まれているにもかかわらず、(1)はUBでもないことを完全に確信しています。それは初期化されていないメモリを指していますか?

(1) unsafe { ptr::write_unaligned(&mut ((*uninit_foo.as_mut_ptr()).bar) as *mut bool, true); }
(2) let x: bool = mem::uninitialized();

もしそうなら、その背後にあるロジックは何ですか(うまくいけば、この問題に関する議論の一部をMaybeUninitのドキュメントに入れることができます)? (1)では逆参照された値は常に「右辺値」のままで「左辺値」になることはないのに対し、(2)では無効なブール値が「左辺値」になるため、実際にはメモリ内で実体化する必要があるためです。 (Rustでこれの正しい用語が何であるかはよくわかりませんが、これらの用語がC ++で使用されているのを見ました)。

そして、そもそもこの混乱を避けるために、フィールドへの生のポインターに直接評価される生のポインターのフィールドアクセス構文のRFCを作成する価値があると考える人はいますか?

ルールが「初期化されていないメモリへの参照を作成したことがない」場合

それがルールであるべきではないと思いますが、そうかもしれません。 現在、UCGで議論されています。

(2)はUBであり、(1)は初期化されていないメモリを指すポインタの参照解除も含まれているにもかかわらず、(1)はUBでもないことを完全に確信していますか?

良い質問! しかし、はい、私たちは-基本的に、せん断の必要性からです。 &mut foo as *mut boolを&raw mut foo 、タイプ*mut boolアトミック式と考えてください。 ここには参照はなく、初期化されていないメモリへの生のptrだけです-そしてそれは間違いなく大丈夫です。

let x: bool = mem::uninitialized();

これはUBです。 参照とは何の関係もありません。

私の暗黙の前提は、 rust-lang / rfcs#2582が上記の例を有効にし、明確に定義することでした。

私はこれに完全に驚いています。 そのRFCは参照のみに関するものです。 なぜそれがブール値について何かを変えると思いますか?

@RalfJung有効なブール値ですぐに上書きされたため、未定義の値が観測できなかったため、UBではないと思ったと思います。 しかし、そうではないと思いますか?

xの値がDropを実装するより複雑な例では、値を上書きするためにrawポインターが必要になるため、UBを回避するためにrfc2582が必要であると考えました。

有効なブール値ですぐに上書きされたため、未定義の値が観測できなかったため、UBではないと思ったと思います。 しかし、そうではないと思いますか?

セマンティクスはステートメントごとに進行します(MIRを見てください)。 すべてのステートメントは意味をなさなければなりません。 let x: bool = mem::uninitialized();は悪いブール値を具体化します、そして後で何が起こるかは問題ではありません-あなたは悪いブール値を具体化してはいけません。

xの値が無効であることは理解していますが、未定義の動作が必要ですか? 私はそれが一般的にどのように文脈から外れることができるかを見ることができます。 しかし、その特定の例のコンテキストでは、動作は明確に定義されていませんか? 私の基本的なハングアップは、「未定義の振る舞い」の意味を完全に理解していないことだと思います。

コンパイラが特定の不変条件に依存できるようにする必要があります。 これらは、常に成立する場合にのみ不変です。 例外を追加し始めると、それは混乱になります。

「値を検査するには、有効性の不変条件を保持する必要がある」という形式の何かを期待しているかもしれません。 ここで、 boolを「検査」すると、 ifます。 これは妥当な仕様ですが、あまり有用ではありません。コンパイラは、不変条件を想定する前に、値が実際に「検査」されていることを証明する必要があります。

それは未定義の振る舞いを必要としますか?

未定義の振る舞いとは何かを選びます。 それは言語の設計の一部です。 未定義の動作自体が「必要」になることはほとんどありませんが、より多くの最適化を有効にする必要があります。 したがって、ここでの技術は、必要な最適化を可能にし、(安全でない)プログラマーの期待に準拠する未定義の動作の定義を見つけることです(それは聞こえるかもしれません^^)。

「未定義の振る舞い」の意味がよくわかりません。

私はそれについてブログ記事を未定義の振る舞いはあなたとコンパイラの間の契約であるということです-そして契約は未定義の振る舞いが起こらないことを確認するのはあなたの義務であると言っています。 それは証明義務です。 「NULLポインターの逆参照はUBです」は、「ポインターが逆参照されるたびに、プログラマーはこのポインターがNULLになる可能性がないことを証明することが期待されます」と言うことと同じです。 これは、コンパイラーがコードを理解するのに役立ちます。ポインターが逆参照されるたびに、コンパイラーは「ああ!ここでプログラマーはポインターがNULLでないことを証明したので、その情報を最適化とコード生成に使用できるからです。ありがとうございます。 、プログラマー!」

契約書に正確に書かれていることは、プログラミング言語次第です。 もちろん、制約があります(たとえば、LLVMによって制約されます)。 私たちの場合、UCGは(言語チームとコンパイラチームから聞いたところによると)契約に次の句を含める必要があると考えています。「右辺値が作成されるたびに、プログラマはこの右辺値が常に存在することを証明する必要があります。妥当性の不変条件を満たします。」 契約にこの条項を含めることを強制する物理法やコンピューター法はありませんが、多くの異なる選択肢の間の合理的な妥協点と見なされます。

特に、弱い契約では正当に発信できなかったLLVMの情報をすでに発信してMaybeUninit使用する必要があります」と「すべてのコードをより少なく最適化できる」のどちらかを選択すると、前者はより良い選択のように。

あなたの例を取る:

let x: bool = mem::uninitialized();

このコードは、今日のrustcのUBです。 mem::uninitialized::<bool>()の(最適化されていない)LLVM IRを見ると、次のようになります。

; core::mem::uninitialized
; Function Attrs: inlinehint nonlazybind uwtable
define zeroext i1 @_ZN4core3mem13uninitialized17h6c99c480737239c2E() unnamed_addr #0 !dbg !5 {
start:
  %tmp_ret = alloca i8, align 1
  %0 = load i8, i8* %tmp_ret, align 1, !dbg !14, !range !15
  %1 = trunc i8 %0 to i1, !dbg !14
  br label %bb1, !dbg !14

bb1:                                              ; preds = %start
  ret i1 %1, !dbg !16
}
; snip
!15 = !{i8 0, i8 2}

基本的に、この関数はスタックに1バイトを割り当ててから、そのバイトをロードします。 ただし、負荷は!rangeでマークされます。これは、バイトが0 <= x <2の間でなければならないこと、つまり0または1のみであることをLLVMに通知します。LLVMはこれが真であり、動作が定義されていないと見なします。この制約に違反した場合。

要約すると、問題は初期化されていない変数自体ではなく、型の制約に違反する値をコピーおよび移動しているという事実です。

博覧会ありがとうございました! 今でははるかに明確です!

私の基本的なハングアップは、「未定義の振る舞い」の意味を完全に理解していないことだと思います。

この一連のブログ投稿(2番目の投稿にはかなり興味深い/怖い例があります)は非常に役立ちます: http :

これには本当に良いドキュメントが必要だと思います。 ここでの変更は、リストできるいくつかの理由と、リストできない他の理由から、おそらく良いことです。 ただし、初期化されていないメモリの正しい使用(およびその他の安全でないメモリの使用)は、非常に直感に反する可能性があります。 Nomiconにはuninitializedに関するセクションがあります(おそらくこのタイプについて話すために更新されるでしょう)が、問題の完全な複雑さを表現していないようです。

(私がそのような文書を書くことを志願しているわけではありません。私は指名します...私よりもこれについてもっと知っている人は誰でも。)

https://github.com/rust-lang/rust/issues/55422#issuecomment -433943803からの興味深いアイデア: into_innerようなメソッドを関数に変換できるため、 MaybeUninit::into_inner(foo)を記述する必要があります。 foo.into_inner() MaybeUninit::into_inner(foo)代わりにfoo.into_inner() -何が起こっているかをはるかに明確に文書化します。

でhttps://github.com/rust-lang/rust/pull/58129私はいくつかのドキュメントを追加してい、返す&mut Tからsetと名前の変更into_innerにinto_initialized 。

この後、 https://github.com/rust-lang/rust/pull/56138が解決されたら、APIの一部(コンストラクター、 as_ptr 、 as_mut_ptr安定化に進むことができると思います。 set 、 into_initialized )。

MaybeUninit::zeroed()がconst fnはないのはなぜですか? ( MaybeUninit::uninitialized()はconst fn )

編集:それは実際に毎晩のRustを使用してconst fnにすることができますか?

MaybeUninit::zeroed()がconst fnはないのはなぜですか? ( MaybeUninit::uninitialized()はconst fn )

@gnzlbg試しましたが、次のいずれかが必要です。

すぐに安定化に移行することについて私が最も心配していることの1つは、実際にこのタイプを使用している人々からのフィードバックがまったくないことです。 誰もが使い始める前にこれが安定するのを待っているようです。 これは、APIの問題に気付くのが遅すぎることを意味するため、問題です。

@ rust-lang / libsメソッドの代わりに関数を使用する通常の条件は何ですか? ここでの操作の一部は、たとえばMaybeUninit::as_ptr(...)ように、人々が記述しなければならない関数である必要があるのではないかと思います。 これがコードを爆破して読めなくなるのではないかと心配していますが、OTOH、 ManuallyDrop一部の関数はまさにこれを実行しました。

@RalfJung私の理解では、ユーザーの型からメソッドを隠すことを避けるために、ジェネリックパラメーターを参照しないものではメソッドは避けられます-したがってManuallyDrop::take 。

MaybeUninit<T>がDeref<Target = T>になることは決してないので、ここでは方法が適切だと思います。

フィードバックを求めれば、あなたがたは受け取るでしょう。 私が使用MaybeUninitの新機能を実装するためにstd最近。

  1. sys / sgx / ext / arch.rsでは、インラインアセンブリと組み合わせて使用​​します。 私は実際にget_mut間違って使用しましたが、参照と生のポインターは同等であると考えていました(928efca1で修正されました)。 私はすでに危険なブロックにいたので、最初は違いに気づきませんでした。
  2. sys / sgx / rwlock.rsでは、これを使用して、 const fn new()のビットパターンがCヘッダーファイルの配列初期化子と同じであることを確認しています。 zeroed続いてsetを使用して、「ドントケア」ビットが0であることを確認しようとしています。これが正しい使用法かどうかはわかりませんが、正常に機能しているようです。 。
  1. out.get_mut() as *mut _ != out.as_mut_ptr()場合、私は非常に混乱します。 本当にC ++っぽく見えます。 なんとか修正されるといいですね。

get_mut()のポイントは何ですか?

最近私が疑問に思っていたのは、 MaybeUninit<T>がTと同じレイアウトであることが保証されているかどうか、そしてそのようなものを使用してヒープの値を部分的に初期化してから完全に変換できるかどうか完全な遊び場)のようなもの

struct Foo {
    x: i32,
}

let mut partial: Box<MaybeUninit<Foo>> = Box::new(MaybeUninit::uninitialized());
let complete: Box<Foo> = unsafe {
    ptr::write(&mut (*partial.as_mut_ptr()).x, 5);
    mem::transmute(partial)
};

Miriによると、この例は機能します(ただし、同じレイアウトのタイプのボックスを変換すること自体が適切かどうかはわかりません)。

@ Nemo157 into_innerがあるのに、なぜ同じメモリレイアウトが必要なのですか?

@Pzixelは、初期化後に値がコピーされないようにするために、スタックに割り当てられた場合にスタックオーバーフローを引き起こす100MBのバッファが含まれていると想像してください。 ただし、テストケースを作成するには、スタックに触れずに初期化されていないボックスを割り当てることができるように、追加のAPI fn uninit_boxed<T>() -> Box<MaybeUninit<T>>必要になるようです。

使用してbox使用しようとしたとき、あなたはこの作品のようなもの核変換を見ることができます初期化されていないヒープ領域を割り当てできるように構文をinto_initializedスタックオーバーフローが発生します遊び場

@ Nemo157コピーを最適化するために、コンパイラを強制する方が良いのではないでしょうか。 とにかくそれを行うべきだと思いますが、コンパイルがそれを行うことを保証する属性があるかもしれません。

@ Nemo157

最近私が疑問に思っていたのは、 MaybeUninit<T>がTと同じレイアウトであることが保証されているかどうか、そしてそのようなものを使用してヒープの値を部分的に初期化してから完全に変換できるかどうか

これは保証されており、コードは有効であると思いますが、いくつかの注意点があります。

  • 使用しているタイプ(特にジェネリックコード)によっては、 ptr::write_unalignedが必要になる場合があります。
  • さらに多くのフィールドがあり、それらの一部のみが初期化される場合は、すべてのフィールドが完全に初期化されるまでTに変換しないでください。

これは、安全なインプレースビルダー抽象化を提供するためにproc-macroと組み合わせることができると私が信じているので、私が興味を持っているユースケースでもあります。

@Pzixel同じメモリレイアウトの場合は、構築したデータ構造全体をコピーすることを回避できます。 もちろん、コンパイラはコピーを削除する可能性があり、小さな構造では問題にならない可能性があります。 しかし、それは間違いなく持っているといいです。

@nicoburnsはい、わかりました。 transmuteと同じように機能することを確認するために、 #[same_layout]や#[elide_copying] 、またはその両方などの属性がある可能性があると言っています。 。 または、 into_constructed実装を変更して、余分なコピーを回避することもできます。 レイアウトに関するドキュメントを読む賢い人だけでなく、これがデフォルトの動作になると思います。 つまり、 into_constructedを呼び出すコードがあり、追加のコピーを取得しますが、 @ Nemo157はtransmuteを呼び出すだけで、問題ありません。 into_constructedが同じことをできない理由はありません。

out.get_mut() as *mut _ != out.as_mut_ptr()場合、私は非常に混乱します。 本当にC ++っぽく見えます。 なんとか修正されるといいですね。

get_mut()のポイントは何ですか?

get_mut()とget_ref()は潜在的に混乱する/未定義の動作を誤って呼び出すのを簡単にする( as_ptr()より安全な代替であるという幻想を与えるため)と上記で同様の点を指摘しました。 as_mut_ptr()ですが、実際にはこれらの方法よりも安全性が低くなります)。

@RalfJungが安定化を提案したAPIのサブセットには含まれて思います(https://www.ralfj.de/blog/2019/02/12/all-hands-recap.htmlを参照)

@RalfJung ptr::freeze()メソッドの提案について(https://www.ralfj.de/blog/2019/02/12/all-hands-recap.html):

MaybeUninitを構築するための同様の方法があることは理にかなっていますか?( MaybeUninit::frozen() 、 MaybeUninit::abitrary()など)。 直感的には、このようなメモリは、 zeroedようにメモリに書き込むコストをかけずに、多くのユースケースで真に初期化されていないメモリと同じくらいパフォーマンスが高いようです。 おそらく、初期化されていないメモリが必要であると人々が本当に確信していない限り、 uninitializedコンストラクタよりも推奨される可能性がありますか?

その点で、「凍結」メモリではなく「初期化されていない」メモリが本当に必要なユースケースは何ですか?

@Pzixel

1. I'd be very confused if `out.get_mut() as *mut _` != `out.as_mut_ptr()`. Looks really C++ish. I hope it would be fixed somehow.

了解しました。 一部の人々がこれを提案する理由は、 &mut !無人と宣言することが役立つ場合があるためです(たとえば、そのような値を持つことはUBです)。 ただし、 MaybeUninit::<!>::uninitiailized().get_mut()を使用して、このような値を作成しました。 そのため、 as_mut_ptr危険性は低くなり、参照の作成が回避されます。

@nicoburns ( freezeは私の考えではないことに注意してください。私はただ議論の一部であり、提案がとても好きです。)

@RalfJungが安定化を提案したAPIのサブセットには思います

正しい。 そして確かに多分私達はそれらを全く持ってはいけません。

MaybeUninitを構築するための同様の方法があることは理にかなっていますか?( MaybeUninit::frozen() 、 MaybeUninit::abitrary()など)。

はい! 基本的なMaybeUninitが安定し、 ptr::freezeが上陸したら、これを追加することを提案しました。

その点で、「凍結」メモリではなく「初期化されていない」メモリが本当に必要なユースケースは何ですか?

これにはさらに調査とベンチマークが必要です。LLVMは他の方法で実行できる最適化を実行しないため、パフォーマンスが低下する可能性があると予想されます。

(時間があれば、他のコメントにも戻ります。)

@Pzixelが事前に割り当てられたメモリにオブジェクトを直接構築できることはbox構文を除く)。 詳細が必要な場合は、削除に関するi.rl.oスレッドから始めるのが最適です。

@nicoburnsが言及しているように、 MaybeUninitは、同じ問題に対する人間工学に基づいていないライブラリベースのソリューションの構成要素として使用できる可能性があり、概念の実験を開始して、それがどのようなAPIであるかを確認する方法として非常に役立ちます。構築を許可します。 それは、 MaybeUninitがそのようなソリューションの構築に必要な保証を提供できるかどうかにかかっています。

@ Nemo157私はそれを一箇所で使用することを提案します、重要な一般的なケースを扱うために何もしません。

@jethrogbどうもありがとう! それで、APIは今あなたのためにうまく機能しているようですか?

2. sys / sgx / rwlock.rsで、 const fn new()のビットパターンがCヘッダーファイルの配列初期化子と同じであることを確認するために使用しています。

うわー、それはクレイジーです。^^しかし、それはうまくいくはずだと思います。結局、引数のないconst fnなので、常に同じものを返すはずです...

最近私が疑問に思っていたのは、 MaybeUninit<T>がTと同じレイアウトであることが保証されているかどうか、そしてそのようなものを使用してヒープの値を部分的に初期化してから完全に変換できるかどうか

最終的に追加する必要があるもののリストには、次のようなものがあります。

fn into_initialized_box(Box<MaybeUninit<T>>) -> Box<T>

これはBoxを変換します。

しかし、はい、私たちはそのような核変換を許可すべきだと思います。 ドキュメントで「これを次のように変換できます」と言う前例はありますか? 私は通常、人々が独自の変換を行うのではなく、ヘルパーメソッドを追加することを好むと思います。

  • 使用しているタイプ(特にジェネリックコード)によっては、 ptr::write_unalignedが必要になる場合があります。

ジェネリックコードでは、フィールドにアクセスできません。 構造体がパックされているかどうかを通常知っているフィールドにアクセスできれば、そうでない場合はptr::writeで十分だと思います。 (割り当ては削除される可能性があるため、使用しないでください!私はそれを忘れ続けています...)

ただし、テストケースを作成するには、スタックに触れずに初期化されていないボックスを割り当てることができるように、追加のAPI fn uninit_boxed<T>() -> Box<MaybeUninit<T>>必要になるようです。

これはバグですが、そのバグを修正するのは難しいかもしれないので、これのために別のコンストラクターを提供することも良い考えかもしれません。 ただし、実装方法がわかりません。 そして、おそらく、スタックスロットをゼロにしてからmemcpyingなどを回避するzeroed_boxようなものも必要です...私はこのすべての複製が好きではありません。 :/

したがって、初期安定化の後/並行して、ヒープ上に初期化されていない記憶のユースケース(基本的にはBoxとMaybeUninit混合)を持っている一部の人々が集まり、最小限のものを設計することを提案しますそのための可能なAPI拡張。 @eddybもこれに関心を示しました。 これは、 mem::uninitialized非推奨にすることとは実際には関係がないので、この(すでに大きすぎる)追跡の問題以外に、独自の議論の場を確保する必要があると思います。

私自身のちょっとしたフィードバック:私は一般的にMaybeUninit<T>に満足しています。 大きな不満はありません。 それはmem::uninitializedよりもフットガンではありません。これは素晴らしいことです。 const newとuninitialized方法は素晴らしいです。 より多くのメソッドがconstであることを望みますが、私が理解しているように、それらの多くは、 const作成する前に、一般にconst fnでさらに進行する必要があります。

TとMaybeUninit<T> 「同じレイアウト」よりも強力な保証が#[repr(transparent)]ですが、属性をユニオンに適用できないことはわかっています)でFFIセーフ(つまり、 TがFFIセーフの場合)である必要があります。 、その後、 MaybeUninit<T>もFFIセーフである必要があります)。 (正直なところ、正のサイズのフィールドが1つしかないユニオンで#[repr(transparent)]を使用できればいいのですが(構造体の場合のように))

私は実際にプロジェクトでMaybeUninit<T>のABIを使用して最適化を支援しています(ただし、危険な方法ではないので、慌てる必要はありません)。 興味のある方がいらっしゃいましたら、詳しく説明させていただきますが、このコメントは簡潔にし、ここでは詳細を省略します。

@mjbshawありがとう!

正のサイズのフィールドが1つしかないユニオンで#[repr(transparent)]を使用できればいいのですが(構造体の場合と同じように)。

その属性が存在するようになったら、それをMaybeUninitに追加するのは簡単です。 実際、このロジックはすでにrustcに実装されています( MaybeUninit<T>は事実上TとABI互換ですが、それを保証するものではありません)。

必要なのは、誰かがRFCを作成してそれを確認し、 repr(transparent)ユニオンに非ZSTフィールドが1つしかないことを確認するチェックを追加することだけです。 試してみませんか? :D

必要なのは、誰かがRFCを作成してそれを確認し、 repr(transparent)ユニオンに非ZSTフィールドが1つしかないことを確認するチェックを追加することだけです。 試してみませんか? :D

@RalfJung尋ねると、あなたがたは受け取るでしょう!

Cc https://github.com/rust-lang/rust/pull/58468

これにより、 maybe_uninitで合理的に安定できると思うAPIのみが残り、残りは個別の機能ゲートに移動します。

さて、準備PRはすべて上陸し、 into_innerもなくなりました。

ただし、安定化する前にhttps://github.com/rust-lang/rfcs/pull/2582を受け入れてMaybeUninit主要なユースケースのようです。 我々はしている非常に近く開始するFCPのために必要なすべてのボックスを有することに。

MaybeUninitを使用するようにコードを変換しました。 私が使用している可能性がかなりの数の場所がありますtake上で動作する方法&mut selfではなくself 。 私は現在x.as_ptr().read()いますが、 x.take()またはx.take_initialized()方がはるかに明確だと思います。

@Amanieuこれは、既存のinto_innerメソッドと非常によく似ています。 たぶん、ここで重複を避けようとすることができますか?

😉

Optionのメソッドtakeには、他のセマンティックがあります。 x.as_ptr().read()はxの内部値を変更しませんが、 Option::take値を置き換えてみます。 それは私にとって誤解を招くかもしれません。

@ qwerty19106 x.as_ptr().read()にMaybeUninit _semantically_はラッパーが再び初期化されていないと葉が値を取り出し、それだけで初期化されていない値が取り残さ取り出した値と同じビットパターンを持っていることが起こります。

私は現在x.as_ptr().read()いますが、 x.take()またはx.take_initialized()方がはるかに明確だと思います。

興味がありますが、その理由を説明していただけますか?

私の見解では、 takeような方法は、 takeとinto_initialized両方とは異なり、2回の服用を防ぐことができないため、多少誤解を招く可能性があります。 実際には、のためにCopy (実際に用タイプCopyのような値None as Option<Box<T>> )、テイクINGのは二回、完全に罰金です! したがって、私の観点からは、 takeとの類似性は実際には当てはまりません。

それをread_initialized()と呼ぶこともできますが、その時点で、それが実際にas_ptr().read()よりも明確であるかどうかを真剣に考えています。

x.as_ptr().read()にMaybeUninit _semantically_が出谷を取り、ラッパーが再び初期化されていない葉は、それだけで初期化されていない値が残し取り出した値と同じビットパターンを持っていることが起こります。

MaybeUninitは実際には有用なセマンティック不変条件がないため、そのステートメントに完全に同意するかどうかはわかりません。 TBH MaybeUninitでの操作を、生の操作効果以外の方法で検討することが有用であるとは確信していません。

@RalfJungうーん、多分「意味論的に」はここでは間違った言葉です。 ユーザーが型をどのように使用するかという点では、値を読み取った後、値が再び初期化されていないと想定する必要があります(型がCopyことが具体的にわかっている場合を除く)。

生の操作上の効果だけを見ると、このような奇妙な相互作用が発生

@RalfJung私の場合はすべて、値が配置され、後で取り出されるstatic mutが含まれます。 スタティックを消費できないため、 into_uninitialized使用できません。

@Amanieu私が尋ねていたのは、なぜx.take_initialized()がx.as_ptr().read()よりも明確だと思いますか?

@ Nemo157

Miriが初期化されていないメモリの長さ0の読み取りを追跡することを期待していましたが、そうではありません。

初期化されていないメモリの長さ0の読み取りは決してUBではないのに、なぜMiriはそれらを気にするのでしょうか。

生の操作上の効果だけを見ると、このような奇妙な相互作用が発生

もちろん、初期化されていないメモリを読み取ることなく、安全性の不変条件に違反する可能性があります。 そのためにMaybeUninit::zeroed().into_initialized()を使用することもできます。 問題は見当たりません。
ここでの「奇妙な相互作用」とは、作成する権利がないタイプの2つの値を作成したことです。 これはすべてSpartacusの安全性不変量に関するものであり、妥当性不変量とは何の関係もありません。

これが、 read_initialized()が何が起こるかをよりよく伝えていると思う理由です。データを読み取り、適切に初期化されていると主張します(これには、そのタイプでこの値を実際に作成できることを確認することも含まれます)。 これは、 MaybeUninitまだ格納されているビットパターンには影響しません。

@RalfJung私は基本的にMaybeUninitをOptionとして扱っていますが、タグはありません。 実際、私は以前、まさにこの目的のためにタグなしオプションのクレートを使用していました。これには、ユニオンから値を抽出するためのtakeメソッドがあります。

@Amanieu @shepmaster https://github.com/rust-lang/rust/pull/58660にread_initializedを追加しました。 それでもtake_initializedよりも良い名前だと思います。 これはあなたのニーズを満たしていますか?

そのPRは、他のいくつかの方法にも例を追加します。フィードバックを歓迎します。

read_initializedに満足しています。

私はそれであったが、私も作っMaybeUninit<T>: Copy場合T: Copy 。 そうしない理由はないようです。

うーん、多分get_initialized方がいい名前でしょうか? 結局のところ、それはsetを補完するようなものです。

または、 set名前をwrite変更する必要がありますか? それはまた一貫性を達成するでしょう。

MaybeUninitを使用するようにコードを変換してきましたが、初期化されていないスライスでの作業は非常に非人間的であることがわかりました。 以下の機能があれば改善できると思います。

  • &mut [T]から&mut [MaybeUninit<T>]への安全な変換。 これにより、 &outパラメータを&mut [MaybeUninit<T>]を使用してエミュレートできます。これは、たとえばread場合に便利です。
  • &mut [MaybeUninit<T>]から&mut [T]への安全でない変換(および&[T]についても同じ)。スライスのすべての要素で.setを呼び出した後に使用されます。

私が持っているAPIは次のようになります。

// The returned slice is truncated to the number of elements actually read.
fn read<T>(out: &mut [MaybeUninit<T>]) -> Result<&mut [T]>;

現在、スライスの操作は人間工学的ではないことに同意します。そのため、 first_ptrとfirst_ptr_mutを追加しました。 しかし、それはおそらく最高のAPIからはほど遠いです。

ただし、最初に「コアAPI」を出荷してから、スライス(およびBox )との相互作用を確認することに集中できればと思います。

setの名前をwriteに変更して、 ptr::writeとの一貫性を保つというアイデアが好きです。

同じように、 read_initializedはreadよりも本当に優れていますか? 誤って使用されて隠されることが懸念される場合は、メソッドではなく関数、つまりMaybeUninit::read(&mut v)ますか? writeについても同じことができます。つまり、一貫性をMaybeUninit::write(&mut v)ために

とにかく、これらのAPIが打ち出されるまで、私は最低限のAPI、つまりnew 、 uninitialized 、 zeroed 、 as_ptr 、 as_mut_ptr安定化を強くサポートします。 get_refとget_mut 。

そして多分get_refとget_mut 。

これらは、 https://github.com/rust-rfcs/unsafe-code-guidelines/issues/77を解決した後でのみ安定させる必要があり、しばらく時間がかかるようです...

最低限のAPI、つまりnew 、 uninitialized 、 zeroed 、 as_ptr 、 as_mut_ptr安定化

私の計画は、 into_initialized 、 set / write 、およびread_initializedをその最小限のセットの一部にすることset / writeとread_initializedは残りの部分と一緒に簡単に実装できるので、私は今、最初のバッチでそれらを安定させないことに傾倒しています。 しかし、最初からinto_initializedようなものを持っていることが望ましい、IMO。

おそらくそれをメソッドではなく関数にします。つまり、 MaybeUninit::read(&mut v) ? writeについても同じことができます。つまり、一貫性をMaybeUninit::write(&mut v)ために

前にここで説明したことから、 Derefインスタンスの問題を回避するために、明示的な関数アプローチのみを使用します。 メソッドの代わりに関数を使用する別の理由で優先順位を導入する必要はないと思います。

read_initializedはreadよりも本当に優れていますか?

良い質問! 知りません。 これはinto_initializedとの対称性のためinto_innerは一般的な方法であり、呼び出されるタイプの概要が失われる可能性があります。 readはそれほど一般的ではありません。 そして多分それはinto_initialized代わりにinitializedであるべきですか? 非常に多くのオプション...

前にここで説明したことから、 Derefインスタンスの問題を回避するために、明示的な関数アプローチのみを使用します。 メソッドの代わりに関数を使用する別の理由で優先順位を導入する必要はないと思います。

ptr::readとptr::writeは関数であり、メソッドではありません。 したがって、優先順位はすでにMaybeUninit::readとMaybeUninit::write優先して設定されています。

編集:さて、どうやらポインタにはreadとwriteメソッドもあります...以前は気づかなかった...しかし、それらはポインタを消費しますが、これはMaybeUninitはあまり意味がありません

非常に多くのオプション...

同意しました。 他の方法でもっと多くの自転車を流すまでは、 new 、 uninitialized 、 zeroed 、 as_ptr 、 as_mut_ptrだけだと思います本当に安定させる準備ができています。

ptr::readとptr::writeは関数であり、メソッドではありません。 したがって、優先順位はすでに設定されています

これらはデータ構造の一部ではありません。もちろん、独立した関数です。 そして、あなたが言うように、それらは今日、メソッドとしても存在します。

しかし、彼らはポインタを消費します

生のポインタはCopyなので、実際には何も消費されません。

生のポインタはCopyなので、実際には何も消費されません。

いい視点ね...

ええと、 v.as_ptr().read()はすでにかなり簡潔で明確です。 as_ptrが続くread 、それはおよそ慎重に考えるべきものとして目立たせる必要があり、ずっとそうよりinto_initializedありません。 個人的には、少なくとも今のところ、 as_ptrとas_mut_ptrのみを公開することに賛成です。 そして、もちろん、 new 、 uninitialized 、およびzeroed 。

@Amanieu &mut MaybeUninit<[T]>と&mut [MaybeUninit<T>]安全な変換がある、 Cellようなものはどうですか?

これにより、次のことが可能になります。これは私にはかなり自然に思えます。

fn read<T>(out: &mut MaybeUninit<[T]>) -> Result<&mut [T]> {
    let split = out.as_mut_slice_of_uninit();
    // ... operate on split ...
    return Some(unsafe { split[0..n].as_uninit_mut_slice().get_mut() })
}

また、呼び出し元に対するセマンティクスをより正確に表しているように感じます。 &mut [MaybeUninit<T>]を取る関数は、私には、どれが大丈夫でどれがそうでないかを区別するロジックがあるように感じます。 一方、 &mut MaybeUninit<[T]>を取ると、セルに既に存在するデータに関しては、セルを区別できないことを表します。

(もちろん、メソッドの名前はバイクシェディングの対象になります-私はCellが行うことを模倣しただけです。)

@eternaleye MaybeUninit<[T]>は、ユニオンをDSTにすることができないため、有効な型ではありません。

うーん、そうだね

他の方法でもっと多くの自転車を流すまでは、 new 、 uninitialized 、 zeroed 、 as_ptr 、 as_mut_ptrだけだと思います本当に安定させる準備ができています。

まあ、私たちは何かを安定させる前にこのRFCを

したがって、実験を待つ間、現在set 、 read_initialized 、およびinto_initializedと呼ばれているものの名​​前について少し自転車に乗ることができます。 次の名前変更が提案されています。

  1. set -> write 。 .as_ptr().read()の最良のメタファーは、「get」ではなく「read」のようですが、補数( .as_ptr_mut().write() )は「set」ではなく「write」である必要があります。
  2. read_initialized -> read 。 writeとうまく一致しますが、安全ではありません。 それ(およびドキュメント)は、データがすでに初期化されていることを手動で確認する必要があるという警告で十分ですか? 安全でないinto_innerでは不十分であるという多くの合意がありました。そのため、名前をinto_initialized 。
  3. into_initialized -> initialized 。 read_initializedとinto_initialized両方がある場合、IMOとの一貫性は良好ですが、 read場合、 into_initializedが少し目立ちます。 メソッド名はかなり長いです。 それでも、私が知っていることから、最も消費する操作はinto_*と呼ばれます。

(1)に対する異議はありますか? そして私は主に(3)にもたれかかっています。 (2)については、私は未定です。 readは入力が簡単ですが、 read_initialized IMOは、そのようなコードを読み取るときにうまく機能します。 実際に初期化されると想定しているところを呼び出すのはいいようです。

考え、意見?

まあ、私たちは何かを安定させる前にこのRFCを

これは私がoffset_of!プラグインを置いた場所ですか? :)

read_initializedはinto_initialized厳密なスーパーセットであることに注意してください( &self代わりにself &self取ります)。 両方をサポートすることは非常に理にかなっていますか?

これは私がoffset_of!プラグインを置いた場所ですか? :)

私のRFCが受け入れられる前にそれを安定させることができれば、確かに。 ;)

両方をサポートすることは非常に理にかなっていますか?

IMOはい。 into_initializedは、同じ値を2回使用することを防ぐため、より安全です。したがって、可能な限りread_initializedよりも優先する必要があります。

したがって、 @ nikomatsakisは以前にこの点を指摘しましたが、ハードブロッカーにはしませんでした。

MaybeUninit<T>とinto_initializedを使用するために多くのコードを移植したところ、不必要に冗長であることがわかりました。 コードは、 mem::uninitializedを使用して「誤って」使用されていた以前よりも、すでにはるかに冗長になっています。

MaybeUninit<T>は単にUninit<T>と呼ばれるべきだと思います。なぜなら、すべての実用的な目的で、未知のMaybeUninit<T>を取得した場合、初期化されていないと想定する必要があるため、 Uninit<T>はそれを正しく要約します。 また、一貫性の理由から、 into_uninitializedはinto_init()または同様のものにする必要があります。

タイプUninitialized<T>とメソッドinto_initialized呼び出すこともできますが、タイプの省略形とメソッドの長い形式を使用するか、またはその逆を行うと、非常に矛盾します。 理想的には、「Rust APIは略語/長い形式を使用する」ということを覚えておく必要があります。それだけです。

略語は人によってあいまいになる可能性があるため、私はどこでも長い形式を使用して、それを1日と呼ぶことを好みます。 しかし、混合物を使用することは、IMOが両方の世界で最悪です。 Rustは長い形式よりも略語を使用する傾向があるため、 Uninit<T>を略語として、 .into_init()をメソッドの別の略語として使用することに反対することはありません。

into_initialized()は好きではありません。値を初期化するために、変換が行われているように聞こえるからです。 私はtake_initialized()方がずっと好きです。 型シグネチャは他のtakeメソッドとは異なることに気付きましたが、意味的にははるかに明確であり、意味の明確さは借用/移動の一貫性に取って代わるべきだと思います。 変更可能な借用であるという優先順位がまだない他の選択肢は、 move_initializedまたはconsume_initializedます。

set()とwrite()については、エイリアスとなるas_ptr().write()との類似性を呼び出すために、 write()を強く支持します。

そして最後に、 take_initialized()などがある場合は、前者の明示性のため、 read() read_initialized()を優先します。

編集:しかし明確にするために、 as_ptr().write()とas_ptr().read()固執することはさらに明確であり、 DANGERDANGER精神回路を引き起こす可能性が高いと思います。

@gnzlbgタイプの名前にFCPがありましたが、そのディスカッションを再開する必要があるかどうかはわかりません。

ただし、 MaybeUninit::uninit()やx.into_init()ように、「init」を一貫して使用するという提案は気に入っています。

into_initialized()は好きではありません。値を初期化するために、変換が行われているように思われるからです。

intoメソッドは、特定のタイプで同じ(所有されている)データを表示する以外に、実際には変換を行わないことがよくあります。たとえば、さまざまなinto_vecメソッドを参照してください。

(into_initに加えて) take_initialized(&mut self)で問題ありませんが、内部状態をundef戻す必要があると思います。

内部状態を元に戻す

https://github.com/rust-lang/rust/issues/53491#issuecomment -437811282

これはselfの内容をまったく変更しないはずです。 所有権だけが譲渡されるため、初期化されていない状態で構築された場合と実質的に同じ状態になります。

これらのことの多くは、200以上の隠されたコメントですでに議論されています。

これらのことの多くは、200以上の隠されたコメントですでに議論されています。

私はしばらく議論を続けていて、誤解されているかもしれませんが、この点は以前に提起されたとは思いません。 特に、引用するコメントは、「内部状態をundef戻す」ことを示唆していませんが、 ptr::read (内部状態を変更しないままにする)と同等にしています。 私が提案しているのは、 mem::replace(self, MaybeUninit::uninitialized())概念的に同等です。

mem::replace(self, MaybeUninit::uninitialized())概念的に同等です。

そのための意味のundefと同等だが、 read : https://rust.godbolt.org/z/e0-Gyu

@scottmcmいいえ、そうではありません。 read場合、次のことが有効です。

let mut x = MaybeUninit::<u32>::uninitialized();
x.set(13);
let x1 = unsafe { x.read_initialized() };
// `u32` is `Copy`, so we may read multiple times.
let x2 = unsafe { x.read_initialized() };
assert_eq!(x1, x2);

提案されたtakeでは、 x2はundefなるため、これは違法になります。

2つの関数が同じアセンブリを生成するからといって、それらが同等であるとは限りません。

ただし、コンテンツをundef上書きしてもメリットはありません。 それは人々が自分自身を足に撃ち込むためのより多くの方法を紹介するだけです。 @jethrogbやる気を出していないのですが、なぜこれが良い考えだと思うのか説明して

(into_initに加えて) take_initialized(&mut self)で問題ありませんが、内部状態をundef戻す必要があると思います。

以前の名前が操作をより正確に説明していると思うので、 into_initialized(self)代わりにtake_initialized(self)を提案していました。 繰り返しますが、 takeは通常&mut selfを取り、 self intoは通常self取りますが、意味的に正確な命名は一貫して入力するよりも重要であると私は信じていますネーミング。 ただし、 move_initializedやtransmute_initializedなど、別の名前を使用する必要があるかもしれません。

また、 v.write()とv.read_initialized()については、 v.as_ptr().write()とv.as_ptr().read()超える正の値は見られません。 後者の2つは誤用される可能性が低いようです。

また、 v.write()とv.read_initialized()については、 v.as_ptr().write()とv.as_ptr().read()超える正の値は見られません。 後者の2つは誤用される可能性が低いようです。

v.write() (またはv.set()または最近私たちが呼んでいるもの)は安全です。 v.as_ptr().write()はunsafeブロックが必要ですが、これはちょっと面倒です。 v.read_init()とv.as_ptr().read()については同意しますが。 v.read_init()は不要のようです。

前の名前が操作をより正確に説明していると思うので、into_initialized(self)の代わりにtake_initialized(self)を提案していました。 繰り返しになりますが、takeは通常&mut selfを取り、intoは通常selfを取りますが、一貫して型指定された命名よりも意味的に正確な命名の方が重要であると思います。

into_init(ialized)もここでは意味的に正確であると強く感じています。結局、 MaybeUninit消費します。

@mjbshawああ、そうです。 気づかなかった...さて、その場合は、 set / writeに関する以前のコメントをすべて取り消します。 たぶんset方が理にかなっています。 CellとPinすでにsetメソッドを定義しています。 主な違いは、 MaybeUninit::setが以前に保存された値をドロップしないことです。 おそらくそれはまだwrite近いです...私は知りません。 いずれにせよ、ドキュメントはかなり明確です。

@RalfJungさて、 take...忘れてください。 move... 、 consume... 、 transmute...などの新しい名前はどうですか? into_init(ialized)は混乱しすぎると思います。 私も、値が初期化されていることを意味しますが、実際には、値がすでに初期化されていることを暗黙的に表明しています。

実際には、すでに初期化されていると暗黙的に主張している場合。

into_init主張する唯一のことは、値がTの_validity invariant_を満たすことであり、これをTと混同しないことを再度指摘する価値があると思います。

例えば:

pub mod foo {
    pub struct AlwaysTrue(bool);
    impl AlwaysTrue { 
        pub fn new() -> Self { Self(true) }
        /// It is impossible to initialize `AlwaysTrue` to false
        /// and unsafe code can rely on `is_true` working properly:
        pub fn is_true(x: bool) -> bool { x == self.0 }
    }
}

pub unsafe fn improperly_initialized() -> foo::AlwaysTrue {
    let mut v: MaybeUninit<foo::AlwaysTrue> = MaybeUninit::uninitialized();
    // let v = v.into_init(); // UB: v is invalid
    *(v.as_mut_ptr() as *mut u8) = 3; // OK
    // let v = v.inti_init(); // UB v is invalid
    *(v.as_mut_ptr() as *mut bool) = false; // OK
    let v = v.into_init(); // OK: v is valid, even though AlwaysTrue is false
    v
}

ここで、 improperly_initializedの戻り値は、 Tの_validity invariant_を満たすという意味では「初期化」されますが、 T _safetyinvariant_を満たすという意味ではありません。 、および区別は微妙ですが重要です。この場合、この区別はimproperly_initializedをunsafe fnとして宣言する必要があるためです。

ほとんどのユーザーが「初期化」されていることについて話すとき、彼らは通常、 MaybeUninit::into_initの「有効だが多分安全ではない」セマンティクスを持っていません。

これらについて非常に冗長にしたい場合は、 Invalid<T>とUnsafe<T> 、 Invalid<T>::into_valid() -> Unsafe<T>使用して、ユーザーにuninit.into_valid().into_safe()記述を要求することができます。 そして、上記のimproperly_initialized返されますUnsafe<T> 、およびユーザーが適切の値を設定した後にのみAlwaysTrueにtrue彼らが実際に安全なTを得ることができます。

// note: this is now a safe fn
fn improperly_uninitialized() -> Unsafe<foo::AlwaysTrue>;
fn initialized() -> foo::AlwaysTrue {
    let mut v: Unsafe<foo::AlwaysTrue> = improperly_uninitialized();
    unsafe { v.as_mut_ptr() as *mut bool } = true;
    unsafe { v.into_safe() }
}

これにより、 improperly_uninitializedが安全なfn improperly_uninitializedになることに注意してください。これは、 AlwaysTrueが安全ではないという不変条件が、関数の周りの「コメント」ではなく、タイプ。

痛々しいほど耐え難いアプローチを追求する価値があるかどうかはわかりません。 MaybeUninit目標は、妥協して、ユーザーが初期化されていない無効なメモリを処理できるようにすることですが、ユーザーの顔にこれらの違いを持たせることはありません。 個人的には、明示的に顔を出さない限り、ユーザーがこれらの違いを知っているとは期待できないと思います。 MaybeUninit正しく使用するには、この違いを知っている必要があります。 そうでなければ、人々はfn improperly_uninitialized() -> AlwaysTrueを安全なfn fn improperly_uninitialized() -> AlwaysTrueとして書き、安全でないAlwaysTrue返すかもしれません。なぜなら、彼らはそれを「初期化」したからです。

Invalid<T>とUnsafe<T>でできることの1つは、 ValidityCheckeableとUnsafeCheckeable 2つの特性があり、 ValidityCheckeable::is_valid(Invalid<Self>)とUnsafeCheckeable::is_safe(Unsafe<Self>)で、 Invalid::into_valid Unsafe::into_safeメソッドとassert_validity!とassert_safety!があります。

コメントに安全性の不変条件を記述する代わりに、チェックのコードを記述するだけで済みます。

into_initが主張する唯一のことは、値がTの有効性不変量を満たしていることであり、一般的な意味でTが「初期化」されていることと混同しないことを再度指摘する価値があると思います。

これは正しいです。 OTOH、最初の説明では、「初期化された」がこれの合理的なプロキシであると感じています。

そうしないと、fn improperly_uninitialized()-> AlwaysTrueを安全なfnとして記述し、安全でないAlwaysTrueを返す可能性があります。

これが適切に「初期化」されていないことを合理的に指摘できると思います。 これらの2つの不変条件がどこかでどのように相互作用するかについての適切なドキュメントが必要であることに同意します(そして、最適な場所が何であるかはわかりません)が、ほとんどの人の直感では、 improperly_uninitializedは大丈夫ではないと言うでしょう。エクスポートする関数。 「他の人の不変条件を破る」というのは、「私がエクスポートするすべての安全な関数は、安全なコードがそれらを使用して大混乱を引き起こすことができないようなものでなければならない」と考えると自然に生じる概念だと思います。

Invalidでできることの1つと危険ValidityCheckeableとUnsafeCheckeableの2つの特性があり、ValidityCheckeable :: is_valid(Invalid)およびUnsafeCheckeable :: is_safe(Unsafe)、Invalid :: into_validメソッドとUnsafe :: into_safeメソッドassert_validityがあります! そしてassert_safety! それらの上に。

ほとんどの場合、安全性の不変条件はチェックできません。 妥当性の不変条件でさえ、参照をチェックできない可能性があります。 (これは、私たちが物事をどのように因数分解するかに少し依存します。)

@scottjmaddox

move ...、consum ...、transmute ...などの新しい名前はどうですか? into_init(ialized)は混乱しすぎると思います。 私も、値が初期化されていることを意味しますが、実際には、値がすでに初期化されていることを暗黙的に表明しています。

move_initは、 into_initよりも「アサーション」をどのように伝えますか?

assert_init(italized)は以前に提案されています。

ただし、 readまたはread_initializedまたはas_ptr().readも、実際には何も主張していないことに注意してください。

これらについて非常に冗長にしたい場合は、 Invalid<T>とUnsafe<T> 、 Invalid<T>::into_valid() -> Unsafe<T>使用して、ユーザーにuninit.into_valid().into_safe()記述を要求することができます。 そして、上記のimproperly_initialized返されますUnsafe<T>ユーザーが適切の値に設定し、後にのみAlwaysTrueにtrue彼らが実際に安全なTを得ることができます。

@gnzlbgねえ、それはかなり気の利いたものです。 これが避けられない方法でユーザーの顔に区別を投げかけるのが好きです。 それはおそらく良い教えの瞬間です。 人々に二度考えさせる「妥当性」と「安全性」? uninit.into_valid().into_safe()は、 uninit.assume_initialized()などと比較して、とにかく冗長ではありません。 もちろん、この区別をするためには、最初にモデルに関する合意を見つける必要があります。 😅このモデルをもう少し調査する必要があると思います。

assert_init(italized)は以前に提案されています。

@RalfJung @eternaleye (私は思う)のためにassume_initializedもあります。 かなり説得力のある正当化のリストについては、 https: //github.com/rust-lang/rust/issues/53491#issuecomment-440730699を参照して

TBH2つのタイプがあるのは冗長すぎるように感じます。

@RalfJungそれをもっと深く掘り下げることができますか? おそらく、高度な冗長性を示していると思われる例をいくつか比較してみてください。

うーん...もっと冗長なAPIを検討している場合は、

uninit.into_inner(uninit.assert_initialized());

セマンティックに非常にうまく機能する可能性があります。 最初のメソッドは、アサーションを記録するトークンを返します。 2番目のメソッドは内部型を返しますが、それが有効であることを表明している必要があります。

しかし、抽象化によって人々が混乱し、間違いを犯す可能性があるため、これが追加の努力の価値があると完全に確信しているわけではありません。

@eternaleyeのためにassume_initializedもあります(私は思います)。 かなり説得力のある正当化のリストを含む#53491(コメント)を参照してください。

フェア。 assume_initializedは私にはいいですね。

または多分それはassume_initですか? それはおそらく、コンストラクタと整合的であるべきであるMaybeUninit::uninit() VS MaybeUninit::uninitialized() -そして一つは、我々はすぐにその呼び出しを行う必要がありますので、最初のバッチで安定化することが予定されていること。

@nicoburnsここにトークンを介して間接参照を追加することで得られるメリットはわかりません。

それをもっと深く掘り下げることができますか? おそらく、高度な冗長性を示していると思われる例をいくつか比較してみてください。

ええと、それが「ただの」 MaybeUninitよりも冗長であることは明らかですよね? 追加の精神的負担(2つのタイプを理解する必要がある)がたくさんあり、二重のアンラッピングがあり、それは私がどちらのタイプを使用するかを選択しなければならないことを意味します。 したがって、ここには追加のコストがあり、正当化する必要があると思います。

私は実際、一般的にUnsafeの有用性を疑っています。 コンパイラーの観点からは、それは完全にNOPになります。 コンパイラは、データが安全性の不変条件を満たしているとは決して想定しません。 ライブラリ実装の観点から、 Vec実装で、一時的に安全性の不変条件に違反するたびに、コードをUnsafe<Vec<T>>に変換すると、コードの可読性が向上するかどうかは非常に疑わしいです。 そして、教育の観点から、有効であるが安全ではないVec<T>を作成し、それを安全なコードに渡すと、すべてが爆発したときに、誰もが驚かれるとは思えません。
これをコンパイラの観点から必要なMaybeUninitと比較してください。また、独自のプライベートコードの「悪い」 boolに注意する必要があるという事実は、一部の人にとっては驚きかもしれません。 。

そのかなりのコストを考えると、 Unsafeはもっと強いモチベーションが必要だと思います。 バグの防止やコードの可読性の向上に実際にどのように役立つかわかりません。

MaybeUninitをMaybeInvalidに名前変更するための引数を見ることができます。 ただし、「無効」は非常にあいまいです(何に対して無効ですVec 」は次の場合に有効であると思われるかもしれません。あらゆる種類の使用法。 「初期化されていない」とは、少なくともほとんどの人にとって基本的に正しい関連付けをトリガーします。 たぶん、「妥当性不変」を「初期化不変」などに名前変更する必要がありますか?

さらに、 Unsafe<T>が存在するだけでは、このラッパーの外部に安全でない値を含めることに対して強力な広範な規則を採用しない限り、誤解を招く可能性があります(ラップされていないすべての値が安全であると誤って示唆することにより)。 これは大規模なプロジェクトであり、別のRFCとより広範なコミュニティのコンセンサスが必要になります。 私はそれがいくぶん物議を醸すと予想し( @RalfJungは上記に対していくつかの正当な理由を与えました)、UBが関与していないのでMaybeUninitよりも弱い議論があります-それは本質的にスタイルの質問です。 そのため、RFCが受け入れられ、標準ライブラリとドキュメントが更新されたとしても、そのような規則がRustコミュニティで普遍的になるかどうかについては懐疑的です。

したがって、コンベンションが行われることを望んでいるIMOは、 MaybeUninit APIをバイクシェッドするよりも大きな魚を揚げることができます。そのプロセスの解決を待つために、安定化をさらに遅らせないことをお勧めします。 MaybeUninit<T> -> T変換を安定させた場合、将来のRust世代はMaybeUninit<Unsafe<T>>を書き込んで、最初に初期化されていないデータを示し、初期化された後も安全ではない可能性があります。

@RalfJung

または多分それはassume_initですか? これはコンストラクターと一致している可能性があります。 MaybeUninit::uninit()対MaybeUninit::uninitialized() -そして、最初のバッチで安定化する予定なので、すぐに呼び出す必要があります。

タイプ、コンストラクター、および-> T関数との-ializedサフィックスがないので、 ::uninit()と.assume_init()がおそらく行く方法だと思います。

ええと、それが「ただの」 MaybeUninitよりも冗長であることは明らかですよね?

状況によって異なります... foo.assume_init().assume_safe() (または、簡潔にする傾向がある場合はfoo.init().safe() )はそれほど長くはないと思います。 必要に応じて、組み合わせをfoo.assume_init_safe()として提供することもできます。 この組み合わせには、2つの仮定を詳しく説明するという利点があります。

追加の精神的負担(2つのタイプを理解する必要がある)がたくさんあり、二重のアンラッピングがあり、それは私がどちらのタイプを使用するかを選択しなければならないことを意味します。 したがって、ここには追加のコストがあり、正当化する必要があると思います。

うまくいけば、複雑さは、妥当性と安全性の背後にある基本的な概念を理解する必要があることに起因します。 それが行われると、私はそれ以上の精神的な複雑さはないと思います。 根底にあるコンセプトは、それを伝えるために重要だと思います。

私は実際、一般的にUnsafeの有用性を疑っています。 コンパイラーの観点からは、それは完全にNOPになります。 コンパイラは、データが安全性の不変条件を満たしているとは決して想定しません。

承知しました; コンパイラのPOVからは役に立たないことに同意します。 この区別による有用性は、一種の「セッションタイプ」インターフェイスとしてのものです。

そのかなりのコストを考えると、 Unsafeはもっと強いモチベーションが必要だと思います。 バグの防止やコードの可読性の向上に実際にどのように役立つかわかりません。

私の目を引いたのは、教えやすさです。 .assume_init()は「OK、妥当性の不変条件を確認したので、良いTができた」という意味だと人々が考えると、間違いは必ず起こると思います。 MaybeUninit<T>の現在のスキームは、このように役に立たないものです。 しかし、私は名前としてUnsafe<T>とInvalid<T>と結婚していません。 名前が何であれ、2つのタイプに分けることは教育的に役立つと思います。 おそらく、現在のフレームワーク内でこれを補うことができるドキュメントを強化するなど、他の方法がありますか?

MaybeUninit名前をMaybeInvalidに変更するための引数を_見ることができます_。 ただし、「無効」は非常にあいまいです(_what _?には無効)。「有効」と「安全」の区別に混乱している人を見かけます。「有効なVec 」は次の場合に有効であると思われるかもしれません。あらゆる種類の使用法。 「初期化されていない」とは、少なくともほとんどの人にとって基本的に正しい関連付けをトリガーします。 たぶん、「妥当性不変」を「初期化不変」などに名前変更する必要がありますか?

私は、「有効性」と「安全性」が「有効性」の響きによって混乱していることに間違いなく同意します。 私は「妥当性」の代わりに「マシン不変」を、「安全性」を「型システム不変」に置き換えてきました。

@rkruppe

したがって、コンベンションが行われることを望んでいるIMOは、 MaybeUninit APIをバイクシェッドするよりも大きな魚を揚げることができます。そのプロセスの解決を待つために、安定化をさらに遅らせないことをお勧めします。 MaybeUninit<T> -> T変換を安定させた場合、将来のRust世代はMaybeUninit<Unsafe<T>>を書き込んで、最初に初期化されていないデータを示し、初期化された後も安全ではない可能性があります。

良い点、特に再。 MaybeUninit<Unsafe<T>> ; タイプ名の冗長性を減らすために、タイプエイリアスを追加することもできます。

タイプ、コンストラクター、および-> T関数との3方向の一貫性を保つことができれば、さらに良いでしょう。 タイプには-ializedサフィックスがないので、:: uninit()と.assume_init()がおそらく行く方法だと思います。

同意しました。 intoプレフィックスを失うのは少し悲しいですが、それを保持する良い方法がわかりません。

では、 read / read_initはどうでしょうか。 ptr::readとの類似性は、「これが実際に初期化されていることを確認した方がよい」というトリガーに十分ですか? read_initは、 into_initと同様の問題がありますか?それは、仮定としてではなく、初期化されるように聞こえますか? assume_initはreadようになりますか?

うまくいけば、複雑さは、妥当性と安全性の背後にある基本的な概念を理解する必要があることに起因します。 それが行われると、私はそれ以上の精神的な複雑さはないと思います。 根底にあるコンセプトは、それを伝えるために重要だと思います。

Vec何かがこれを適切に使用して、 Vecの不変条件に違反した場合を反映する場合、コード例を挙げていただけますか? それは非常に冗長で、実際に何が起こるかを完全に曖昧にするだろうと思います。

このようなタイプを追加することは、根底にある概念を伝えるための間違った方法だと思います。

.assume_init()が「OK;妥当性の不変条件をチェックしたので、良いTができた」という意味だと人々が考えると、間違いが必ず起こると思います。

「このVec<i32>を0xFFでいっぱいに書き込んで初期化したので、初期化されたので、プッシュできる」というような人はほとんどいないと思います。 少なくとも、これが実際に人々が犯す間違いであるという、より確かなデータの兆候を見たいと思います。
私の経験では、人々は、未知のコードにデータを配ったり、一部のデータに対してライブラリ操作を呼び出したりするときに、ライブラリの不変条件を維持する必要があるというかなり堅実な直感を持っています。

ここでは物事が少し落ち着きました。 では、次の計画についてはどうでしょうか。

  • MaybeUninit::uninitializedを廃止し、名前をMaybeUninit::uninit変更するPRを準備します。
  • それが着陸したら(stdsimdを更新する必要があるので、これがうまくいかないと人々が考える場合はここに時間があります)、 MaybeUninit::{new, uninit, zeroed, as_ptr, as_mut_ptr}を安定させるためのPRを準備します。

これにより、 set / write 、 into_init[ialized] / assume_init[ialized] 、およびread[_init[italized]]に関する質問が未解決のままになります。 現在、私はassume_init 、 write 、 readに傾いていますが、これについては以前に考えを変えました。 残念ながら、ここで決定を下す方法がわかりません。

  • それが上陸したら

それは、(a)非推奨の警告または(b)不安定な機能の使用なしに、初期化されていない値を作成する手段がない期間があることを意味しますか? それは持続可能な慣行ではありません。

効果的に削除する予定のないものを非推奨にする場合は、非推奨の警告が追加されるたびに、安定した代替品を利用できるようにする必要があります。 それ以外の場合、人々は警告を無視して自分たちの生活を続けるために注釈を追加するだけです。

それは、(a)非推奨の警告または(b)不安定な機能の使用なしに、初期化されていない値を作成する手段がない期間があることを意味しますか?

私は混乱しています。 不安定なメソッドを廃止し、代わりに別の不安定なメソッドを導入することを提案しています。

私が話していたことに注意してくださいMaybeUninit::uninitializedではない、 mem::uninitialized 。

残念ながら、ここで決定を下す方法がわかりません。

@RalfJung他の名前変更PRで以前に行ったように、それを実行します(そして、必要に応じてr?私も実行します)。 :)

以前に他の名前変更PRで行ったように、それを実行してください(そして、必要に応じて私に教えてください)。 :)

さて、これらは最初の安定化の一部である必要はないので、少し待つつもりです。

不安定なメソッドを廃止し、代わりに別の不安定なメソッドを導入する

ああ、落とし穴。 それでは続けてください。

了解しました。https : ます。

初期化されていない->初期化
into_initialized-> assume_init
read_initialized->読み取り
セット->書き込み

新しく提案された名前が好きです。 readが悪用されるのではないかと少し心配していますが、主にptr::readとの関連付けが原因で、 into_initializedが悪用される可能性ははるかに低いようです。 全体として、新しい命名は安定化のために完全に受け入れられると思います。

MaybeUninit :: {new、uninit、zeroed、as_ptr、as_mut_ptr}を安定させるためにPRを準備します。

これが1.35ベータ版になる可能性はありますか(約2週間で)?

https://github.com/rust-lang/rfcs/pull/2582がまだ完全に空中にあることを考えると、これをプッシュすることについて少し矛盾してい
OTOH、 MaybeUninitは十分長い間待っていました。 そして、人々が現在書いている段階的初期化のコードが、 MaybeUninit書くものよりも優れているのとは異なります。

とはいえ、 https://github.com/rust-lang/rust/pull/59284はまだ着陸していないので、これを1.35にするために急いでいる必要があります。 TBH人々が新しいメソッド名で遊んで、彼らがどのように感じるかを見ることができるように、もう1サイクル待つことを望みます。

上の構築機能というチャンスがあるMaybeInit可能性がconst ?

initとnewはconstです。 zeroedはそうではありません。 constなる前に、const関数が実行できることを拡張する必要があります。

MaybeUninitについてフィードバックを提供したかったのですが、実際のコードの変更はhttps://github.com/Thomasdezeeuw/mio-st/pull/71で確認でき

私が遭遇した唯一の小さな問題は、 MaybeUninit::set &mut Tを返すと、 let _ = ...を使用する必要があるということ

また、多くの場合Cと組み合わせて、ユニタライズされた配列を操作するときに必要なAPIを追加する必要があります。

  1. &mut [MaybeUninit<T>]から&mut [T]する方法があれば便利です。ユーザーは、スライス内のすべての値が適切に初期化されていることを確認する必要があります。
  2. uninitialized_arrayようなパブリック配列イニシャライザー関数またはマクロも非常に便利です。

MaybeUninitについてフィードバックを提供したかった

どうもありがとう!

MaybeUninit :: setで&mut Tを返すと、let _ = ..を使用する必要があります。

どうして? 戻り値を「捨てる」ことができます。実際、ドキュメントの例let _ = ...は実行されません。 ( write / setはまだ例がありません...しかし実際にはreadとほとんど同じです、多分それはただリンクするべきです。)

foo.write(bar); letなくても問題なく動作します。

ユニタライズされた配列での作業

ええ、それは間違いなく将来の関心のある分野です。

@RalfJung

MaybeUninit :: setで&mut Tを返すと、let _ = ..を使用する必要があります。

どうして? 戻り値を「捨てる」ことができます。実際、ドキュメントの例let _ = ...は実行されません。 ( write / setはまだ例がありません...しかし実際にはreadとほとんど同じです、多分それはただリンクするべきです。)

unused_resultsの警告を有効にしたので、 let _ = ...ないと警告が表示されます。 それがデフォルトではないことを忘れました。

ああ、私はその警告について知りませんでした。 面白い。

これは、 writeが参照を返さないようにするための引数である可能性があり、さらに需要がある場合は、そのための別のメソッドを提供します。

uninitialized_arrayようなパブリック配列イニシャライザー関数またはマクロも非常に便利です。

これは[MaybeUninit::uninit(); EVENTS_CAP]ます。 https://github.com/rust-lang/rust/issues/49147を参照して

それがデフォルトではないことを忘れました。

これは、 writeが参照を返さないようにし、さらに需要がある場合はそのための別のメソッドを提供するための引数になる可能性があります。

ニッチのようですか? 将来さらに需要がある場合は、参照を返さ

ニッチのようですか?

ええ、値を設定し、それへの可変参照を返すメソッドはたくさんあります。

@Centril Heh、他の場所でこれを書いたときに、ここであなたのコメントを見たとは思わない: https :

https://github.com/rust-lang/rust/pull/59912で廃止された古い名前変更された関数を削除し

その後、次にやるべきことは安定化を提案することだと思います...:tada:

rust-lang / rfcs#2582がまだ完全に空中にあることを考えると、これをプッシュすることについて少し矛盾しています。 :/そのRFCがなければ、構造体の段階的な初期化はまだ不可能ですが、とにかく人々はそれを行います。
OTOH、 MaybeUninitは十分長い間待っていました。 そして、人々が現在書いている段階的初期化のコードが、 MaybeUninit書くものよりも優れているのとは異なります。

その後、次にやるべきことは安定化を提案することだと思います...🎉

@RalfJungここのドキュメントの状態はどうですか? 私がよりよく眠るのを助けるいくつかの明確な文書で「人々はとにかくそれをするだろう」を軽減することができれば... :)

MaybeUninitのドキュメント、特にassume_initのドキュメントを読むと、「安全性」セクションで、 mu.assume_init()を呼び出して、その結果を返すかどうかが明確ではありません。安全なfnでは、安全性の不変条件も維持する必要があります。 安定させる前に、これらのドキュメントを拡張し、ライブラリが提供する安全性の不変条件のスニペットを提供することをお勧めします。これは、 MaybeUninitを使用するときにも支持する必要があります。

ここでのドキュメントの状態はどうですか? 私がよりよく眠るのを助けるいくつかの明確な文書で「人々はとにかくそれをするだろう」を軽減することができれば... :)

おそらく、構造体の段階的な初期化に関するセクションを追加し、現在はサポートされていないと述べます。 これを読んでいる人は「WTF、ほんと?」みたいな感じになります。

TBHこれはかなりイライラします。 :(私たちは今までにそれについていくつかのアドバイスを思い付くことが非常に可能だったと思います、そして私たちはそれをすることができなかったのは悲しいです。

「安全性」セクションでは、mu.assume_init()を呼び出して、その結果を安全なfnに返す場合、安全性の不変条件も維持する必要があることは明らかではありません。 安定させる前に、これらのドキュメントを拡張し、MaybeUninitを使用するときにも支持する必要があるライブラリ提供の安全不変条件のスニペットを提供することをお勧めします。

基本的に、これをデータ型不変条件の全体的な考え方と、それがRustでどのように機能するかを説明するドキュメントに変換することを提案しています。 MaybeUninitはそのための間違った場所だと思います。 それは、この懸念が実際にはそうではないのにMaybeUninitに固有であるように聞こえるでしょう。 あなたが求めていることは、ノミコンのようなもっと高いレベルの場所で説明されるべきです。 MaybeUninitのドキュメントを、このタイプの主要な問題に焦点を当てる予定です。 それが役に立つと思うなら、それらを自由に拡張してください。 :)

基本的に、これをデータ型不変条件の全体的な考え方と、それがRustでどのように機能するかを説明するドキュメントに変換することを提案しています。

これは少し強力です... MaybeUninit<T>のドキュメントの戦略的な場所で、「ああ、 __ちなみに__、安全性の不変の問題もください」と提案しているだけです。 小説を追加することを提案しているわけではありません。 ;)その小説はノミコンに存在することができますが、 MaybeUninit<T>を使用するほとんどの人は、ほとんどの場合、標準ライブラリのドキュメントとインターフェイスします。

了解しました。これらすべてを安定化PRに組み込んでみました: https :

私はちょうどの利用につまずいたmem::uninitializedの標準ライブラリのドキュメントで、本当に他にどこことに注意することは知らなかったの最後の例core::ptr::drop_in_placeニーズが皮肉のようなものも(更新しますhttps://github.com/rust-lang/rfcs/pull/2582によってのみ認可される他の形式のUBを示しているので、個人的には削除します)。

@HeroicKatoraありがとう! その修正をhttps://github.com/rust-lang/rust/pull/60445に組み込みました

現在、ref-to-unaligned-fieldについては何もできませんが、ドキュメントを削除するのが良いかどうかはわかりません。

メタデータに部分的に基づいてデータを初期化するトレイトPartialUninit (またはPartialInit )を追加するかもしれません。

例: MODULEENTRY32W 。
最初のフィールド( dwSize )は、構造体サイズ( size_of::<MODULEENTRY32W>() )で初期化する必要があります。

pub trait PartialUninit: Sized {
    fn uninit() -> MaybeUninit<Self>;
}

impl<T> PartialUninit for T {
    default fn uninit() -> MaybeUninit<Self> {
        MaybeUninit::uninit()
    }
}

impl PartialUninit for MODULEENTRY32W {
    unsafe fn uninit() -> MaybeUninit<MODULEENTRY32W> {
        let uninit = MaybeUninit { uninit: () };
        uninit.get_mut().dwSize = size_of::<MODULEENTRY32W>();
        uninit
    }
}

あなたはどのように思いますか?

@kgv申し訳ありませんが、あなたの提案がわかりません。 おそらく、あなたが解決しようとしている問題を説明するいくつかの追加のコンテキストが役立つ可能性がありますか? そして多分あなたの提案された解決策のより完全な例?

@scottjmaddoxが修正されました。 はっきりしていますか?

@kgvこれが解決している問題は何

構造体の割り当てベースの部分的な初期化は、削除する必要のない型に対してのみ機能することに注意してください。 それ以外の場合、 uninit.get_mut().foo = barはfooドロップします。これはUBです。

@RalfJung私が解決しようとしている問題self依存しません( Selfまたは何にも依存しません(定数))。たとえば、フィールドの1つはSelfサイズです。

@kgvこのようなユースケースはヘルパーモジュールまたはクレートによってより適切に処理されるという点で、ここで@RalfJungに同意する

安定化PRは、ベータ版にちょうど間に合うように着陸しました。 :)ユニオンと初期化されていないメモリの状況を調査し始めてから、約8か月が経ちました。そして、最終的には6週間で出荷されるものができました。 なんて旅だ! それを手伝ってくれたすべての人に感謝します。 :D

もちろん、私たちはまだ終わっていません。 解決すべきhttps://github.com/rust-lang/rfcs/pull/2582がありmem::uninitialized (主にプラットフォーム固有のコード)の使用法がまだかなりあります。 我々が何をすべきかを把握する必要があります:私たちが今持っている安定したAPIは非常に最小限であるreadとwrite 、そして我々は助けをの配列や箱での作業というのAPIを考え出す必要がありますMaybeUninit 。 そして、エコシステム全体をmem::uninitializedからゆっくりと移動させるために行うべき説明がたくさんあります。

しかし、そこにたどり着きます。この最初のステップはおそらく最も重要なステップでした。 :)

そして、 MaybeUninit配列とボックスの操作に役立つAPIを考え出す必要があります。

@RalfJungそのために; たぶん今がhttps://github.com/rust-lang/rust/issues/49147で作業を開始する時

また、残りのビットの小さい問題を優先して、この追跡の問題を分割して閉じる必要があります。

そのために; 多分今が#49147に取り組み始める時ですか? = P

ボランティアをしましたか? ;)(私はそのための時間がないのではないかと心配しています。)

おそらく、この追跡の問題を分割して閉じ、残りのビットの問題を小さくする必要があります。

それはプロセスの専門家にお任せします。 しかし、私は同意する傾向があります。

ボランティアをしましたか? ;)(私はそのための時間がないのではないかと心配しています。)

私は何をしましたか... = D-私はすでに取り組んでいるプロジェクトを持っているので、おそらくしばらく時間がかかるでしょう。 多分誰か他の人が興味を持っていますか? (もしそうなら、追跡の問題に飛び乗ってください)

それはプロセスの専門家にお任せします。 しかし、私は同意する傾向があります。

それは私だろう...;)私はそれを分割してすぐに閉じようとします。

@RalfJungは、 let x: bool = mem::uninitialized();がUBであるというあなたの声明について、問題は、なぜ無効なプリミティブがUBであると見なされるのかということです。 私が理解しているように、UBをトリガーすることは無効であることを確認するために、値を読み取る必要があります。 しかし、あなたがそれを読まなければ、それなら何ですか?

価値を創造することさえ悪いことだと思いますが、さびがとにかくそれを許さない理由を知りたいですか? 無効な状態を観察しなくても害はないようです。 それは初期のエラーのためだけですか、それとも何か他のものですか?

これらの仮定に依存しているコンパイラに実際のケースはありますか?

たとえば、 foo(x: bool)ような関数に注釈を付けて、 xが有効なブール値であることをLLVMに通知します。 これにより、関数が元々 xいなくても、 trueまたはfalseではないboolを渡すことがUBになります。 これは、コンパイラーが以前に使用されていない変数の使用を導入したい場合があるため便利です(特に、ループが少なくとも1回実行されることを証明せずに、ステートメントをループから移動するときに発生します)。

また、関数の境界だけでなく、関数内でこれらのアノテーションの一部を設定(または設定したい)します。 そして、将来、そのような情報が役立つ場所がもっと見つかるかもしれません。 「変数を使用する」という巧妙な定義(定義せずに使用した用語であり、定義するのは確かに簡単ではありません)でそれをカバーできるかもしれませんが、安全でないコードのUBに関してはそうだと思います。可能な場合は簡単なルールを用意することが重要です。

したがって、安全でないコードであっても、コード内の型が何かを意味することを確認したいと思います。 これは、初期化されていないメモリを専用の型で適切に処理することによってのみ可能です。変数の内容についてコンパイラに嘘をつくというアドホックな「yolo」アプローチではありません(「これはboolだと主張します、しかし実際には初期化しない」)。

たとえば、foo(x:bool)のような関数に注釈を付けて、xが有効なブール値であることをLLVMに通知します。 これにより、関数が元々xを参照していなくても、trueまたはfalseではないboolを渡すことがUBになります。 これは、コンパイラーが以前に使用されていない変数の使用を導入したい場合があるため便利です(特に、ループが少なくとも1回実行されることを証明せずに、ステートメントをループから移動するときに発生します)。

これは使用法と見なすことができます。 値を初期化して、有効な値で上書きされる前に、値を読み取ったり渡したりしないことについて質問しています。
このような微妙な方法で値を初期化するための有用なユースケースは見当たりませんが、疑問に思っています。

一言で言えば、私の質問は、このコードがUBであるかどうか(ドキュメントによると-それです)、もしそうなら、私がそう書くと正確に何が壊れるかということです

let _: bool = unsafe { mem::unitialized };

サブジェクト自体に関する別の質問:ヒープに直接メモリを割り当てることができるbox構文があり、メモリをスタックすることがあるBox::new()とは異なり常に機能することがわかっています。 では、 box MaybeUninit::new()からそれを入力すると、 Box<MaybeUninit<T>>をBox<T>に変換するにはどうすればよいでしょうか。 核変換を書くべきですか、それとも何ですか? たぶん、私は単にドキュメントのこの点を見逃しただけです。

@Pzixelは、このスレッドですでにBoxとMaybeUninit間の相互作用について実際に説明しました:smile:

@Centrilには、これを分割するときに良いかもしれない議論するサブ

はい、その議論は覚えていますが、特定のAPIは覚えていません。

一言で言えば、私は次のようなものが欲しいです

fn into_inner<A,T>(value: A<MaybeUninit<T>>) -> A<T> { unsafe { std::mem::transmute() } }

しかし、そのようなAPIはないと思います。言語の進化のこの時点では、コンパイラのサポートなしでは実装できなかったようです。


もう少し考えてみたところ、どのレベルのネストでも機能するはずです。 したがって、 Vec<Result<Option<MaybeUninit<u8>>>>は、 into_innerを返すVec<Result<Option<u8>>> into_innerメソッドが必要です。

get_refとget_mutが同時に安定化されると想定していました(すべての機能がこの問題を指摘しています)。 そうしない理由はありますか? それらは素晴らしく、実行するアクションの実行が許可されていることを示す唯一の指標です(これは明らかにtrueである必要があります)。

これは使用法と見なすことができます。

したがって、 let x: bool = mem::uninitialized()はboolを使用していません( x割り当てられている場合でも!)。

fn id(x: bool) -> bool { x }
let x: bool = id(mem::uninitialized());

それを使用しますか? どうですか

fn uninit() -> bool { mem::uninitialized() }
let x: bool = uninit();

ここでの返品は用途ですか?

これはすぐに非常に微妙になります。 したがって、私たちが与えるべき答えは、すべての割り当て(MIRに下げた後のすべての割り当てのように、実際にはすべてのコピー)が使用であり、 let x: bool = mem::uninitialized()割り当てが含まれるということです。


get_refとget_mutが同時に安定化されると想定していました(すべての機能がこの問題を指摘しています)。 そうしない理由はありますか? それらは素晴らしく、実行するアクションの実行が許可されていることを示す唯一の指標です(これは明らかにtrueである必要があります)。

これはhttps://github.com/rust-lang/unsafe-code-guidelines/issues/77の解決時にブロックされ&mut boolを持っていても安全ですか? 答えは「はい」であるべきだと思いますが、人々は同意しません。

これは、rust-lang / unsafe-code-guidelines#77の解決時にブロックされます

ブロッキングが発生する必要はないと思います。 それを安定させて「メモリが初期化されていない場合にこれを使用するのはUBです」と言ってから、問題がないと判断した場合は後で要件を緩和することができます。 これは、初期化後に使用するのに適した方法です。

その後、要件を緩和します

つまり、将来のバージョンのドキュメントに対してコーディングしたが、誰かが(API互換!)古いバージョンのコンパイラを使用してコードをコンパイルした場合、UBが存在するということですか?

@Gankro

ブロッキングが発生する必要はないと思います。 それを安定させて「メモリが初期化されていない場合にこれを使用するのはUBです」と言ってから、問題がないと判断した場合は後で要件を緩和することができます。 これは、初期化後に使用するのに適した方法です。

それは私には非常に足がかりのようです。 &mut *foo.as_mut_ptr()書いてみませんか? すべてを初期化したら、なぜそれが機能しないのでしょうか? IOW、私は今あなたが言っていることについて疑問に思っています

彼らが実行するアクションの実行が許可されていることを示す唯一の兆候

なぜそうではないのでしょうか? 値を初期化した後でできることをすべて網羅的にリストすると、長いリストになります。^^

@shepmaster

つまり、将来のバージョンのドキュメントに対してコーディングしたが、誰かが(API互換!)古いバージョンのコンパイラを使用してコードをコンパイルした場合、UBが存在するということですか?

人々が&mut *foo.as_mut_ptr()なら、それは今日真実です。 私はそれを避ける方法がわかりません。

また、そのドキュメントを作成するときに実際に何かを変更する必要がある場合にのみ、UBがあります。 そうしないと、保証を行う前に同じコードが同じコンパイラで実行された場合にUBが存在するという奇妙な状況に

そうです、私はそのプロセスが

  • 厳格だが実装でそれを安定させる-今は意味のない要件
  • メモリモデルの作業に進み、何をしますか
  • モデルが完成したら

    • UBにする必要がある場合は、クールで、ドキュメントは同じままにし、役立つ場合は最適化を追加します

    • UBである必要がない場合は、かっこいいです。ドキュメントから削除して、1日と呼んでください。

@RalfJung

ここでの返品は用途ですか?

はい、値を返すか、どこにでも渡すことは使用法です。

これはすぐに非常に微妙になります。 したがって、私たちが与えるべき答えは、すべての割り当て(MIRに下げた後のすべての割り当てのように、実際にはすべてのコピー)が使用であり、let x:bool = mem :: uninitialized()の割り当てが含まれるということです。

有効に見えます。

とにかく、それは仲裁MaybeUninitの入れ子についてですか? ユーザーがすべてのラッパータイプの変換を作成しなくても、安全に変換できますか?

@Pzixelあなたの質問を理解できるかどうかはわかりませんが、 https://github.com/rust-lang/rust/issues/61011で議論されていると思い

まだ安定していないMaybeUninit::write()メソッドはunsafeはありませんが、すでに存在するTでdropの呼び出しをスキップできますが、これは安全ではないと想定していました。 これが安全であると考えられる前例はありますか?

https://doc.rust-lang.org/nomicon/leaking.html#leaking
https://doc.rust-lang.org/nightly/std/mem/fn.forget.html

Rustの安全保証には、デストラクタが常に実行されるという保証が含まれていないため、 forgetはunsafeとしてマークされていません。

ただし、 https://github.com/rust-lang-nursery/nomicon/issues/135も参照して

MaybeUninit<T> -> NonNull<T>メソッドをMaybeUninitに追加できますか? AFAICT MaybeUninit::as_mut_ptr() -> *mut Tによって返されるポインタがnullになることはありません。 これにより、 NonNull<T>を使用するAPIとのインターフェースが必要になることを減らすことができます。

let mut x = MaybeUninit<T>::uninit();
foo(unsafe { NonNull::new_unchecked(x.as_mut_ptr() });

に:

let mut x = MaybeUninit<T>::uninit();
foo(x.ptr());

MaybeUninit :: as_mut_ptr()-> * mutTによって返されるポインタがnullになることはありません。

これは正しいです。

一般的に(そして@Gankroがこれを言うのを見たと思いますが)、 NonNull 「静止状態」でかなりうまく機能しますが、実際にポインターを使用するときは、できるだけ早く生のポインターに到達したいと考えています。 それははるかに読みやすいです。

ただし、 NonNullを返すメソッドを追加することは問題ないようです。 しかし、それは何と呼ばれるべきですか? 優先順位はありますか?

https://github.com/rust-lang/rust/issues/47336の前例があり

https://github.com/rust-lang/rust/pull/60445#issuecomment -488818677に記載されているクレーターの実行は発生しましたか?

@centrilが言及している3か月の利用可能時間という考えは、ベータ版、安定版、夜間のすべてで警告なしになりたい人には実現しません。 1.36.0は1週間以内にリリースされ、nightlyはすでに警告を発しています。

非推奨は1.40.0に延期される可能性がありますか?

非推奨の警告は、それらを担当するクレートに常に分離されているわけではありません。 たとえば、クレートが内部でstd::mem::uninitializedを使用するマクロを公開する場合でも、サードパーティのクレートによる使用は非推奨の警告を呼び出します。 今日、プロジェクトの1つをnightlyコンパイラでコンパイルしたときにこれに気づきました。 コードにuninitialized記述がimplement_vertexマクロが呼び出されたため、非推奨の警告が表示されました。

gliumマスターでcargo +nightly testを実行すると、1400行を超える出力が得られます。これは、ほとんどがuninitialized関数の非推奨警告で構成されています(警告を200回カウントしますが、 rg "uninitialized" | wc -l出力は561です)。

残りのメソッドの安定化をブロックする残りの懸念は何ですか? *foo.as_mut_ptr()を介してすべてを実行するのは非常に面倒であり、場合によっては( write )必要以上のunsafeブロックが必要になります。

エミュレートすること@SimonSapin writeあなたが全体置き換えることができ、 MaybeUninitなし、安全でない使用して*val = MaybeUninit::new(new_val)ところval: &mut MaybeUninit<T>とnew_val: Tあなたが使用できるかを古い値が必要な場合はstd::mem::replace 。

@ est31これらは良い点です。 1、2回のリリースで非推奨を後押ししても大丈夫です。

異議はありますか?

1.36.0リリースのブログ投稿ですでに述べています。

多分UninitとしてRust 1.38以降、より安全な代替手段であり、関数mem :: uninitializedは非推奨になります。

そのため、これは良いメッセージを送信せず、混乱を招くため、これをフリップフロップすることは避けるべきだと思います。 さらに、廃止日はブログ投稿で言及されていることを考えると、広く知られているはずです。

uninitialized非推奨に戻るのは遅いかもしれません。 しかし、交換がしばらくの間Stableチャネルで行われた後、Nightlyでのみ非推奨の警告を発行するポリシーを決定できるでしょうか。

たとえば、Firefoxは、リリースから2週間後に新しいRustバージョンをました。

1.36.0リリースのブログ投稿ですでに述べています。

私は、ブログ投稿での日付の言及がそのような鉄壁の程度であることに同意しません。 リポジトリにあり、編集を送信できます。

そのため、これは良いメッセージを送信せず、混乱を招くため、これをフリップフロップすることは避けるべきだと思います。

「フリップフロップ」は悪いことですが、データとフィードバックに基づいて考え

私は実際の決定についてはあまり気にしませんが、提案によって人々が混乱することはないと思います。 ブログ投稿または非推奨の警告を見た人は、新しいものに移動できます。 ただ気にしない人は、次のいくつかのリリースを気にしません。

「フリップフロップ」は悪いことですが、データとフィードバックに基づいて考えを変えることはそれではありません。

完全に同意した。 「非推奨のスケジュールが少し厳しすぎたので、リリースまでに元に戻しました」という悪いメッセージが送信されているのはわかりません。 実際には、まったく逆です。
実際、IIRCは、安定化PRの着陸時に、前例は2ではなく将来3つのリリースを非推奨にすることであると述べましたが、何らかの理由で2を使用しました。3つのリリースは、stable-gets-released-with-the -非推奨-発表と非推奨-毎晩、それは毎晩追跡する人々にとって公正な時間のようです。 6週間は1年ですよね? ;)

そのため、非推奨バージョンを1.39.0に変更するPRを明日提出する予定です。 人々がそれが重要であると思うならば、私はそのブログ投稿を更新するためにPRを提出することもできます。

そのため、非推奨バージョンを1.39.0に変更するPRを明日提出する予定です。 人々がそれが重要であると思うならば、私はそのブログ投稿を更新するためにPRを提出することもできます。

1.39に同意しますが、遅くとも同意します。 ブログ投稿に加えて、リリースノートも更新する必要があります。

非推奨スケジュールの変更についてPRを提出: https :

@SimonSapin

残りのメソッドの安定化をブロックする残りの懸念は何ですか? * foo.as_mut_ptr()を介してすべてを実行することは非常に面倒であり、場合によっては(書き込みの場合)必要以上に安全でないブロックが含まれます。

as_ref / as_mut場合、参照が初期化されたデータを指す必要があるかどうかがわかるまで、正直に待ちたかったのです。 それ以外の場合、これらのメソッドのドキュメントは非常に予備的なものです。

read / write場合、名前と署名が意味をなすものであることに全員が同意すれば、問題なく安定させることができます。 これはManuallyDrop::take/readと調整する必要があると思いますが、おそらくManuallyDrop::writeもあるはずですか?

正直なところ、参照が初期化されたデータを指している必要があるかどうかがわかるまで待ちたかったのです。

安全でないコードガイドラインWGと言語チームがこの問題について決定を下すには何が必要ですか? 数週間、数ヶ月、または数年で発生する可能性が高いと思いますか?

それまでの間、 as_mutが不安定であっても、ユーザーが何かを行う必要があるときに&mut *manually_drop.as_mut_ptr()書くのを止めることはできません。

安全でないコードガイドラインWGと言語チームがこの問題について決定を下すには何が必要ですか? 数週間、数ヶ月、または数年で発生する可能性が高いと思いますか?

数ヶ月、多分数年。

それまでの間、as_mutが不安定であっても、ユーザーが何かを実行する必要があるときに&mut * manually_drop.as_mut_ptr()を記述できなくなるわけではありません。

はい、知っています。 希望は、 &mut部分を可能な限り遅らせ、生のポインターを操作するように人々を誘導することです。 もちろん、 https://github.com/rust-lang/rfcs/pull/2582がないと、難しいことがよくあります。

MaybeUninitのドキュメントは、少なくともこれが言語のセマンティクスのあいまいさであり、ユーザーはそれがOKではないと控えめに想定する必要があることを議論するのに最適な場所のようです。

確かに、それは他の選択肢でしょう。

控えめな仮定でも、値が完全に初期化された後、 as_mutは有効です。

配列を保守的にする1つの方法は、 MaybeUninit<[MaybeUninit<Foo>; N]>です。 外側のラッパーを使用すると、1回のuninit()呼び出しで配列を作成できます。 ( [expr; N]リテラルにはCopyが必要だと思いますか?)内部ラッパーは、配列をトラバースするためにslice::IterMutの便利さを使用するという控えめな仮定でも、安全になります。次に、 Foo値を1つずつ初期化します。

@SimonSapinは、 uninitialized_array!マクロを。

@RalfJung多分uninit_array!がより良い名前でしょう。

@Stargateur絶対に、これは現在の名前で確実に安定することはありません。 https://github.com/rust-lang/rust/issues/49147がすぐに発生した場合(TM)、安定することはないでしょう。

@RalfJungうーん、それは私のせいです、私は大きな理由なしにPRをブロックしていました: https :

@eddybこれはlibcoreで機能します。 しかし、どういうわけか、liballocでこの機能を使おうとすると、フラグを設定してもコンパイルされません。 https://github.com/rust-lang/rust/commit/4c2c7e0cc9b2b589fe2bab44173acc2170b20c09を参照して

Building stage1 std artifacts (x86_64-unknown-linux-gnu -> x86_64-unknown-linux-gnu)
   Compiling alloc v0.0.0 (/home/r/src/rust/rustc.2/src/liballoc)
error[E0277]: the trait bound `core::mem::MaybeUninit<K>: core::marker::Copy` is not satisfied
   --> <::core::macros::uninit_array macros>:1:32
    |
1   |   ($ t : ty ; $ size : expr) => ([MaybeUninit :: < $ t > :: uninit () ; $ size])
    |   -                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `core::marker::Copy` is not implemented for `core::mem::MaybeUninit<K>`
    |  _|
    | |
2   | | ;
    | |_- in this expansion of `uninit_array!`
    | 
   ::: src/liballoc/collections/btree/node.rs:109:19
    |
109 |               keys: uninit_array![_; CAPACITY],
    |                     -------------------------- in this macro invocation
    |
    = help: consider adding a `where core::mem::MaybeUninit<K>: core::marker::Copy` bound
    = note: the `Copy` trait is required because the repeated element will be copied

error[E0277]: the trait bound `core::mem::MaybeUninit<V>: core::marker::Copy` is not satisfied
   --> <::core::macros::uninit_array macros>:1:32
    |
1   |   ($ t : ty ; $ size : expr) => ([MaybeUninit :: < $ t > :: uninit () ; $ size])
    |   -                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `core::marker::Copy` is not implemented for `core::mem::MaybeUninit<V>`
    |  _|
    | |
2   | | ;
    | |_- in this expansion of `uninit_array!`
    | 
   ::: src/liballoc/collections/btree/node.rs:110:19
    |
110 |               vals: uninit_array![_; CAPACITY],
    |                     -------------------------- in this macro invocation
    |
    = help: consider adding a `where core::mem::MaybeUninit<V>: core::marker::Copy` bound
    = note: the `Copy` trait is required because the repeated element will be copied

error[E0277]: the trait bound `core::mem::MaybeUninit<collections::btree::node::BoxedNode<K, V>>: core::marker::Copy` is not satisfied
   --> <::core::macros::uninit_array macros>:1:32
    |
1   |   ($ t : ty ; $ size : expr) => ([MaybeUninit :: < $ t > :: uninit () ; $ size])
    |   -                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `core::marker::Copy` is not implemented for `core::mem::MaybeUninit<collections::btree::node::BoxedNode<K, V>>`
    |  _|
    | |
2   | | ;
    | |_- in this expansion of `uninit_array!`
    | 
   ::: src/liballoc/collections/btree/node.rs:162:20
    |
162 |               edges: uninit_array![_; 2*B],
    |                      --------------------- in this macro invocation
    |
    = help: the following implementations were found:
              <core::mem::MaybeUninit<T> as core::marker::Copy>
    = note: the `Copy` trait is required because the repeated element will be copied

error: aborting due to 3 previous errors

謎が解けました:libcoreでの繰り返し式の使用は、実際にはコピーであるタイプに対するものでした。

また、liballocで機能しない理由は、 MaybeUninit::uninitが昇格できないためです。

@RalfJungたぶん、完全に不要なマクロの使用を削除するPRを開きますか?

@eddybhttps://github.com/rust-lang/rust/pull/62799の一部を作成しました。

maybe_uninit_ref

as_ref / as_mutの場合、参照が初期化されたデータを指す必要があるかどうかがわかるまで、正直に待ちたかったのです。 それ以外の場合、これらのメソッドのドキュメントは非常に予備的なものです。

そのため、不安定なget_ref / get_mutは間違いなくお勧めです。 ただし、 MaybeUninitが初期化されているときに、 get_ref / get_mutが使用される場合があります。これは、(現在知られている初期化された)データを安全に処理するためです。 memcpy ( assume_init代わりに、 memcpyトリガーする可能性があります)。

  • これは特に特殊な状況のように思われるかもしれませんが、初期化されていないデータを使用する(したい)主な理由は、まさにこの種の安価な節約のためです。

このため、 assume_init_by_ref / assume_init_by_mutがあればいいと思います( into_innerはassume_initと呼ばれているので、 refはもっともらしいと思いますref mutゲッターもこれを反映するために特別な名前を取得します)。

Drop相互作用に関連して、これには2つまたは3つのオプションがあります。

  1. get_refおよびget_mutとまったく同じAPI。ドロップグルーがあるとメモリリークが発生する可能性があります。

    • (バリアント): get_ref / get_mutと同じAPIですが、 Copyバインドされています。
  2. ドロップを保証するためのクロージャースタイルAPI:

impl<T> MaybeUninit<T> {
    /// # Safety
    ///
    ///   - the contents must have been initialised
    unsafe
    fn assume_init_with_mut<R, F> (mut self: MaybeUninit<T>, f: F) -> R
    where
        F : FnOnce(&mut T) -> R,
    {
        if mem::needs_drop::<T>().not() {
            return f(unsafe { self.get_mut() });
        }
        let mut this = ::scopeguard::guard(self, |mut this| {
            ptr::drop_in_place(this.as_mut_ptr());
        });
        f(unsafe { MaybeUninit::<T>::get_mut(&mut *this) })
    }
}

( scopeguardのロジックは簡単に再実装できるため、それに依存する必要はありません)


明示的なassume_init要件がある場合、これらはget_ref / get_mutよりも速く安定する可能性があります。

欠点

オプション.1バリアントが選択され、 get_ref / get_mutがassume_init状況なしで使用可能になると、このAPIはほぼ厳密に劣ります。 (提案されたAPIを使用すると、参照からの読み取りは問題ないため、 get_refおよびget_mutの場合は決して可能ではありません)

@danielhenrymantillaがget_{ref,mut}について書いたのと同様に、 readはおそらくread_initまたはread_assume_init程度に名前を変更する必要があると思い始めています。これは、初期化が完了した後にのみ実行できます。

@RalfJungこれについて質問があります:

fn foo<T>() -> T {
    let newt = unsafe { MaybeUninit::<T>::zeroed().assume_init() };
    newt
}

たとえば、 foo<NonZeroU32>と呼びます。 これは、 foo関数を宣言するときにUBをトリガーしますか(すべてのTに対して有効である必要があるため、またはUBをトリガーするタイプでインスタンス化する場合)?質問する。

@Pzixelコードは、実行時にのみUBを引き起こす可能性があります。

したがって、 foo::<i32>()で問題ありfoo::<NonZeroU32>()はUBです。

可能なすべての呼び出し方法で有効であるという特性は「健全性」と呼ばれます。てください。 Rustの一般的な契約では、ライブラリの安全なAPIサーフェスは健全でなければなりません。 これにより、ライブラリのユーザーはUBについて心配する必要がなくなります。 Rustの安全性に関するストーリー全体は、健全なAPIを備えたライブラリに基づいています。

@RalfJungありがとう。

したがって、正しく取得できれば、この関数は正しくありません(したがって、無効です)が、 unsafeとしてマークすると、この本体は有効で健全になります。

@Pzixel安全でないとマークした場合、健全性はもはや適用される概念ではありません。 「この音ですか」は、安全なコードの質問としてのみ意味があります。

はい、一部の入力はUBをトリガーする可能性があるため、関数unsafeをマークする必要があります。 ただし、そうしても、これらの入力はUBをトリガーするため、関数をそのように呼び出さないでください。 安全でないコードであっても、UBをトリガーすることは決して大丈夫ではありません。

はい、もちろん、私はそれを理解しています。 部分的な機能はunsafeとしてマークする必要があると結論付けたかっただけです。 私には理にかなっていますが、あなたが答える前に私はそれについて考えていませんでした。

この追跡の問題に関する議論は非常に長いので、まだ不安定なMaybeUninit各機能について、他のいくつかの追跡の問題に分解できますか?

  • maybe_uninit_extra
  • maybe_uninit_ref
  • maybe_uninit_slice

合理的なようです。 https://github.com/rust-lang/rust/issues/63291もあり

MaybeUninit<T>より一般的に追跡するメタ問題を支持してこれを閉じる:#63566

このページは役に立ちましたか?
0 / 5 - 0 評価