このガイドについて
std 開発ガイドへようこそ。
このガイドはライブラリチームによって保守されています。
このガイドはまだあまり完成していません。 このガイドへの貢献を大いに歓迎します。
その他の有用なドキュメント:
ライブラリクレートのビルドとデバッグ
標準ライブラリは同じビルドシステムでビルドされるため、rustc-dev-guide の手順のほとんどは 標準ライブラリにも適用されます。そのため、先に読むことをおすすめします。
alloc と core の println デバッグ
alloc と core ではロギングや IO API が利用できないため、コンパイラの他の部分向けのアドバイスは
ここでは適用できません。
代わりに、テストすべきコードを通常のクレートに抽出してそこでデバッグ文を追加するか、 POSIX システムでは次のハックを使用できます。
#![allow(unused)]
fn main() {
extern "C" {
fn dprintf(fd: i32, s: *const u8, ...);
}
macro_rules! dbg_printf {
($s:expr) => {
unsafe { dprintf(2, "%s\0".as_ptr(), $s as *const u8); }
}
}
fn function_to_debug() {
let dbg_str = format!("debug: {}\n\0", "hello world");
dbg_printf!(dbg_str.as_bytes().as_ptr());
}
}
その後、デバッグ対象のコードを実行するテストを実行し、次のようにしてエラー出力を表示できます。
./x.py test library/alloc --test-args <test_name> --test-args --nocapture
ライブラリの最適化とベンチマーク
推奨読書: The Rust performance book
何を最適化するか
実際のコードで重要なものとして現れるコードを最適化することが推奨されます。
たとえば、Command::spawn の小さなアロケーションを削るよりも、[T]::sort を高速化するほうが有益です。
後者は syscall のコストが支配的だからです。
遅いライブラリコードに関する issue には I-slow T-libs、 コードサイズに関する issue には I-heavy T-libs というラベルが付けられています。
ベクトル化
現在、core と alloc ではベースラインのターゲット機能(例: x86_64-unknown-linux-gnu における SSE2)のみ使用できます。 これは、実行時の機能検出が std でしか利用できないためです。 可能な場合、ベクトル化を実現する推奨される方法は、コンパイラバックエンドの自動ベクトル化パスが理解できるようにコードを形作ることです。 これは、ユーザーの crate がジェネリックなライブラリ関数、たとえばイテレーターをインスタンス化する際に、追加のターゲット機能付きでコンパイルされる場合に利益をもたらします。
rustc-perf
標準ライブラリのうち rustc 自身で頻繁に使用される部分については、 ベンチマークサーバーを使用すると便利な場合があります。
これは crate のコンパイル時間のみを測定し、実行時性能は測定しないため、コンパイラで使用されない機能のベンチマークには使用できません。 たとえば、浮動小数点コード、リンクリスト、mpsc チャネルなどです。 それらについては、明示的なベンチマークを作成するか、実際のコードから抽出する必要があります。
組み込みマイクロベンチマーク
組み込みベンチマークは cargo bench を使用しており、
core と alloc については benches ディレクトリに、std については test モジュール内にあります。
ベンチマークは、ループの多数の反復にわたって実行時間を平均化するため、Bencher::iter によってループ内で自動的に実行されます。
CPU バウンドなマイクロベンチマークでは、単一イテレーションの実行時間はナノ秒からマイクロ秒の範囲に収まるべきです。
特定の は、rustc を再コンパイルせずに
./x bench library/<lib> --stage 0 --test-args <benchmark name> 経由で呼び出せます。
cargo bench は実時間を測定します。多くの場合これで十分ですが、大きな関数内で数命令を節約するような小さな変更は、
システムノイズに埋もれてしまうことがあります。そのような場合、次の変更により実行結果をより再現しやすくできます。
config.tomlでインクリメンタルビルドを無効にする- std とベンチマークを
RUSTFLAGS_BOOTSTRAP="-Ccodegen-units=1"でビルドする - システムを可能な限りアイドル状態にする
- ASLR を無効化する
- ベンチマークプロセスを特定のコアに固定する
- CPU のスケーリングガバナーを
固定周波数のもの(
performanceまたはpowersave)に変更する - 特にノート PC のような熱制限のあるシステムでは、 クロックブーストを無効化する
スタンドアロンテスト
x や cargo のベンチマークハーネスが邪魔になる場合は、ベンチマークを別の crate に抽出すると便利です。
たとえば、perf stat や cachegrind の下で実行する場合です。
標準ライブラリをビルドし、stage0-sysroot を rustup ツールチェーンとしてリンクし、それを使用して変更済みの標準ライブラリでスタンドアロンベンチマークをビルドします。
std の再ビルド時間が高速な反復には長すぎる場合、ベンチマークだけでなく、 テスト対象のコードも別の crate に抽出すると便利です。
perf-record の下での実行
コードを別の crate に抽出するのが現実的でない場合、まずベンチマークをビルドし、その後
perf record の下で再実行して、perf report でベンチマークカーネルまで掘り下げることができます。
# インライン化の変更とコードの並べ替えを減らすための 1CGU、ソース注釈用の debuginfo
$ export RUSTFLAGS_BOOTSTRAP="-Ccodegen-units=1 -Cdebuginfo=2"
# ベンチマークを実行せずにビルドする
$ ./x bench --stage 0 library/core/ --test-args skipallbenches
# perf の下でベンチマークを実行する
$ perf record --call-graph dwarf -e instructions ./x bench --stage 0 library/core/ --test-args <benchmark name>
$ perf report
perf.data の名前を変更して後続の実行で上書きされないようにしておくことで、後で
perf diff を使って、変更済みライブラリでの実行結果と比較できます。
アセンブリの比較
perf report はベンチマークコードのアセンブリを表示しますが、何が変わったのかをうまく把握するのが難しい場合があります。
特に複数のベンチマークが影響を受けた場合はそうです。代替手段として、ベンチマークスイートからアセンブリを直接抽出して diff できます。
# インライン化の変更とコードの並べ替えを減らすための 1CGU、ソース注釈用の debuginfo
$ export RUSTFLAGS_BOOTSTRAP="-Ccodegen-units=1 -Cdebuginfo=2"
# ベンチマークライブラリをビルドする
$ ./x bench --stage 0 library/core/ --test-args skipallbenches
# これにより、次のようなものが出力されるはずです
Running benches/lib.rs (build/x86_64-unknown-linux-gnu/stage0-std/x86_64-unknown-linux-gnu/release/deps/corebenches-2199e9a22e7b1f4a)
# すべてのベンチマークのアセンブリを取得する
$ objdump --source --disassemble --wide --no-show-raw-insn --no-addresses \
build/x86_64-unknown-linux-gnu/stage0-std/x86_64-unknown-linux-gnu/release/deps/corebenches-2199e9a22e7b1f4a \
| rustfilt > baseline.asm
# 変更を含むブランチに切り替える
$ git switch feature-branch
# 上記の手順を繰り返す
$ ./x bench ...
$ objdump ... > changes.asm
# 出力を比較する
$ kdiff3 baseline.asm changes.asm
これはスタンドアロンベンチマークにも適用できます。
ドキュメントの書き方
このドキュメントでは、std/core の公開 API のドキュメントを書く方法を説明します。
まず、一般的な情報から始めましょう。
インラインコードブロックを使用する場合
型やコードに関連するものについて述べる場合は、必ず インラインコードブロックに入れるべきです。念のため、インラインコードブロックはバッククォート (`) で作成します。例:
This a `Vec` and it has a method `push` which you can call by doing `Vec::push`.
intra-doc リンクを使用する場合
intra-doc リンク(この機能の詳しい説明は こちらで確認できます)は、 型が言及される場合は可能な限り使用すべきです。
補足: 項目をドキュメント化している場合、その項目にリンクする必要はありません。
そのため、String::push_str メソッドのドキュメントを書く場合、
push_str メソッドや String 型にリンクする必要はありません。
コードブロック
rustdoc では、コードブロックはテストされます(デフォルトで Rust コードブロックとして扱われるためです)。
これにより、ドキュメントが最新の状態であるかを把握できます。そのため、
コードブロックでは可能な限り ignore の使用を避けてください! Rust 以外の言語として扱いたい場合は、
コードブロックのタグで単にそれを設定してください。
```text
This is not rust code!
```
いくつかの特殊なケース:
- コード例を実行できない場合(たとえば I/O 項目をドキュメント化する場合)は、
no_runを使用します。 - 失敗することが期待される場合は、
should_panicを使用します。 - コンパイルに失敗することが期待される場合(これはかなりまれなはずです!)は、
compile_failを使用します。
コードブロックについての詳細は こちらで確認できます。
モジュールのドキュメントの書き方
モジュールは「類似した」項目を含むことが想定されています。そのため、そのドキュメントは、 モジュールに含まれる項目が何を行うのかを理解するための概要と、最終的には 基礎 を提供することが想定されています。
良い例として、 f32 モジュール や fmt モジュール を参照できます。
関数/メソッドのドキュメントの書き方
ドキュメント化された各メソッド/関数の基本的な形式は、おおよそ次のようになるべきです。
[explanations]
[example(s)]
説明
explanations とは、そのメソッドが何をするのか、そして
各引数が何のためのものなのかを説明するテキストを意味します。例として次のメソッドを見てみましょう。
pub fn concat_str(&self, s: &str) -> String {
if s.is_empty() {
panic!("empty concat string");
}
format!("{}{}", self.string, s)
}
説明は次のようになるべきです。
Returns a new [`String`] which contains `&self` content with `s` added at the end.
Panic?
関数/メソッドが特定の状況で panic する可能性がある場合は、必ず
言及しなければなりません! この説明には Panics というタイトルを前置する必要があります。
# Panics
`concat_str` panics if `s` is empty.
例
例については、関数/メソッドの使用方法を示す必要があります。
panic セクションと同様に、Example というタイトルを前置する必要があります(複数ある場合は複数形)。
最後に assert*! マクロを使用して、例が期待どおりに動作していることを確認するとよいです。
これにより、読者もその関数が何をしているのか(または何を返すのか)をより簡単に理解できます。
# Example
```
let s = MyType::new("hello ");
assert_eq!("hello Georges", s.concat_str("Georges").as_str());
```
その他の項目のドキュメントの書き方
例が(強く)推奨されるものの必須ではない点を除けば、メソッドや関数の場合とほとんど同じです。
良い例では、多くの場合、その項目の作成方法を示します。
機能のライフサイクル
問題の特定
標準ライブラリへの変更を提案する前の最初のステップは、解決しようとしている問題を適切に特定することです。これにより、標準ライブラリへの変更を必要とせず、より良い解決策が存在する XY problem のケースを特定しやすくなります。
このため、API設計に関する理論上の懸念ではなく、実際に人々が直面している現実の問題に焦点を当てることが有用です。
標準ライブラリへの適合性
crates.io 上のサードパーティクレートとは異なり、Rust標準ライブラリはバージョン付けされていません。これは、追加された安定APIは、後方互換性のない方法で決して削除または変更できないことを意味します。このため、標準ライブラリのメンテナーは、標準ライブラリAPIへのあらゆる変更に高い基準を設けています。
標準ライブラリに適しているAPIは、言語やコンパイラのサポートを必要とするもの、または既存の標準ライブラリ型を拡張するものです。時間とともに進化することが想定される複雑なAPI(例: GUIフレームワーク)は、バージョン付けがないため適していません。
API変更提案プロセスは、新しいAPIを標準ライブラリに追加するための軽量な最初のステップとなることを意図しています。このプロセスの目的は、提案されたAPI変更が成功する可能性を最大化することです。ACPプロセスは、すべての変更がライブラリAPIチームによってレビューされることを保証することでこれを実現します。ライブラリAPIチームは提案を評価し、その提案がマージされ、最終的なFCPを通過すると楽観視できる場合に受け入れます。
rust-lang/libs-team リポジトリでは、このissueテンプレートを使用してACPを作成できます。これには提案するAPIの概略を含めるべきですが、実装される最終的な設計である必要はありません。
ACPは厳密に必須ではないことに注意してください。提案するAPIの実装を含むpull requestをそのまま提出することもできますが、ライブラリチームが最終的にこの機能を却下した場合、労力が無駄になるリスクがあります。ただし、このリスクはACPが受け入れられた場合でも常に存在することに注意してください。ライブラリチームは、安定化プロセスの後半で機能を却下する可能性があるためです。
API設計の検討
機能が標準ライブラリに含めるのに適していると判断されたら、それをRust APIとして表現する最善の方法を見つけるために、正確な設計を反復的に検討するべきです。この反復的な検討は、コミュニティのすべてのメンバーがコメントし改善を提案できる、Rust internals のようなコミュニティフォーラムで行うべきです。
議論の際には、以下の点を念頭に置いてください。
- 汎用性と具体性のバランスを取るようにしてください。
- 過度に汎用的なAPIは、一般的なユースケースで使いにくくなる傾向があり、APIサーフェスも複雑になります。これによりレビューや保守が難しくなり、外部クレートの方が適している可能性があります。
- 過度に具体的なAPIは、一般的なユースケースをすべてカバーできず、将来的にそれらのユースケースに対応するための追加のAPI変更が必要になる可能性があります。
- 常に検討すべき代替案は、この機能を単にサードパーティクレートで追加することです。標準ライブラリ型に新しいメソッドを追加する場合でも、拡張トレイトを使用することでこれは可能です。
- 既存のAPIで既に可能なことの単なる短縮形である「便利」関数の場合、標準ライブラリのサーフェスを拡張するコストを、新しい関数によるエルゴノミクス上の影響と比較して検討するべきです。
- たとえば、ある型に便利メソッドが多すぎると、ドキュメントを閲覧しにくくなります。
- さらに、言語レベルの改善によって不要になった場合に、このメソッドが将来非推奨になる可能性が高いかどうかを検討してください。
ライブラリチーム自体はこの議論に直接関与しませんが、個々のメンバーがフィードバックを提供するためにコメントすることがあります。ACP以降に大きな変更があった場合、この時点で別のACPを提案し、ライブラリAPIチームに設計を検証してもらうことができます。
実装
API設計空間の検討が済んだら、有力な解決策に基づく実装を rust-lang/rust リポジトリへのpull requestとして提案するべきです。
pull requestには、検討された代替案の概要を含めるべきです。これにより、レビューの一環として同じ検討作業を繰り返すことを避けられるため、レビュアーにとって有用です。これがない状態で提出されたPRは、より多くの代替案を検討するよう求められてクローズされる場合があります。
提案された機能についてACPが提出されていない場合、そのPRは、標準ライブラリへの適合性を判断するためにライブラリAPIチームによってレビューされる必要があります。
追跡と安定化
PRがマージされる前に、その機能の安定化までの進捗を追跡するtracking issueを開くよう求められます。
これには2つの例外があります。
- 既存の不安定APIの変更では、このAPIの既存のtracking issueを再利用できます。
- 即座に安定となる変更(例: 安定型に対するトレイト実装)にはtracking issueは必要ありません。ただし、そのような変更では不安定期間中にAPIを調整する機会がないため、追加の慎重な精査が必要です。
機能の安定化
- ステータス: 現行
- 最終更新日: 2022-05-27
機能の安定化には、#[stable] 属性の追加が伴います。これは、新しいトレイト実装とともに導入される場合もあれば、既存の #[unstable] 属性を置き換える場合もあります。
安定化は Libs FCP(Final Comment Period)プロセスを経て行われます。これは通常、その機能の tracking issue 上で行われます。
FCP が適切なのはいつか?
不安定機能の API 設計空間(例: 代替 API)が完全に検討され、未解決の懸念がない状態になったら、誰でもその安定化を推進できます。
機能が安定化の準備ができているかどうか分からない場合、最初のステップは、関連する tracking issue で質問し、その議論に参加している他の人から支援を受けることです。場合によっては、その tracking issue に他のアクティブな参加者があまりいないこともあります。そのため、フィードバックを得るのに困っている場合は、支援を依頼するために libs チームのレビューアー のいずれかに直接 ping してください。
安定化レポート
機能が安定化の準備ができたら、FCP プロセスの最初のステップは安定化レポートを書くことです。安定化レポートは必須ではありませんが、強く推奨されており、ライブラリ API チームのメンバーが必要だと判断した場合には必須とされることがあります。安定化レポートの目的は、レビューアーがより迅速に判断できるようにし、リリースノートで安定化された API を文書化するプロセスを簡素化することです。安定化レポートは、主に実装履歴、API 概要、経験レポートの 3 つのセクションで構成されます。
実装履歴セクションでは、実装 PR における最初の議論、最初の実装以降にその機能に加えられたすべての変更、その機能の存続期間中に提起されたすべての issue、およびそれらがどのように解決されたかを要約する必要があります。
API 概要セクションには、標準ライブラリに導入される API の正確な説明を含める必要があります。最新の状態であれば、多くの場合、トップレベルのコメントへの単純なリンクで十分です。ただし、安定化レポートの作成者が tracking issue 自体の作成者ではない場合など、状況によっては、古い情報を修正するために元の tracking issue を編集できないことがあります。
libs チームは、このための cargo unstable-api というツールを保守しており、場合によってはこれを使用して API 概要を生成できます。注意 現在のこのツールの実装は壊れやすく、すべての場合に機能するわけではありません。将来的には、rustdoc または rustc 自身の API のいずれかの上に構築された、より永続的なバージョンのこのツールを用意したいと考えています。
経験レポートセクションには、その機能を使いたいと考え、自分たちのニーズに合うことをテストしたユーザーの具体的なユースケースを含める必要があります。経験レポートには、その機能を使用した経験の簡単な要約を含める必要があります。理想的には、その機能をプロジェクトに統合したコミットやブランチへのリンクを含めるべきですが、これは要件ではありません。別の方法として、安定化されるものと同一の API をエクスポートする crate の使用例をユーザーが提供することもできます。
安定化レポートの例は #88581 で確認できます。
機能を安定化する PR を書く前に
まず FCP が完了しているか確認してください。完了していない場合は、rust-lang organization のメンバーであれば @rust-lang/libs-api に ping するか、
その機能の状況について尋ねるコメントを残してください。
これにより、安定化 PR を開いた後、FCP プロセスが進行する間に定期的なリベースが必要になる事態を避けられます。
部分的な安定化
既存の機能のサブセットのみを安定化したい場合は、新しい tracking issue の作成を省略し、代わりに安定化される機能のサブセットに対する部分的な安定化 PR を作成する必要があります。
機能が部分的な安定化の準備ができているかどうか分からない場合、最初のステップは、関連する tracking issue で質問し、その議論に参加している他の人から支援を受けることです。場合によっては、その tracking issue に他のアクティブな参加者があまりいないこともあります。そのため、フィードバックを得るのに困っている場合は、支援を依頼するために libs チームのレビューアー のいずれかに直接 ping してください。
tracking issue #71146 と部分的な安定化 PR #94640 で、機能を部分的に安定化する例を確認できます。
const が関係する場合
const 関数は、#[rustc_const_unstable] 属性を #[rustc_const_stable] 属性に置き換える PR で安定化できます。const 性がコミットしたいものであるかどうかについて意見を求めるために、Constant Evaluation WG に ping する必要があります。公開される intrinsic が const 安定化される場合は、@rust-lang/lang も FCP に含める必要があります。
その関数が内部的に #[allow_internal_unstable] 属性を通じて他の不安定な const 関数に依存しているかどうかを確認し、内部的に不安定な呼び出しが削除された場合にその関数をどのように実装できるかを検討してください。#[allow_internal_unstable] の詳細については、Stability attributes ページを参照してください。
unsafe と const が関係する場合、たとえば「unconst」な操作では、その使用に関する const safety の議論も文書化される必要があります。つまり、const fn には追加の決定性(例: 実行時/コンパイル時の結果が対応していなければならず、関数の出力は入力のみに依存する、など)の制約があり、それらは維持されなければならず、unsafe が使用される場合にはそれらについて論じる必要があります。
ライブラリ機能の安定化 PR
機能を安定化することを決定したら、その安定化を実際に行う PR が必要です。この種の PR は、通常は属性を更新するだけの小さなものなので、Rust に関わるための非常に良い方法です。
以下は、機能を安定化する方法の一般的なガイドです。もちろん、機能はそれぞれ異なるため、一部の機能ではこのガイドで述べている内容を超える手順が必要になる場合があります。
アイテムの安定性属性を更新する
ライブラリアイテムは、次のように #[unstable] 属性によって不安定としてマークされます。
#[unstable(feature = "total_cmp", issue = "72599")]
pub fn total_cmp(&self, other: &Self) -> crate::cmp::Ordering { ... }
これを、バージョンをプレースホルダー CURRENT_RUSTC_VERSION に設定した #[stable] 属性に変更する必要があります。
#[stable(feature = "total_cmp", since = "CURRENT_RUSTC_VERSION")]
他の #[stable] 属性には明示的なバージョン番号が含まれている場合がありますが、pull request がマージされるまでに古くなる可能性があるため、バージョン番号を明示的に書いてはいけません。
doctest から feature gate を削除する
安定化されるアイテム上のすべての doctest は不安定機能を有効にしています。そのため、安定化された今では、それらの属性は不要であり、削除する必要があります。
/// # 例
///
/// ```
-/// #![feature(total_cmp)]
-///
/// assert_eq!(0.0_f32.total_cmp(&-0.0), std::cmp::Ordering::Greater);
/// ```
これらを見つける最も明らかな場所は item 自体ですが、ライブラリ全体を検索する価値があります。多くの場合、テストでそれを使用していた他の不安定なメソッドも見つかります。
コンパイラから feature gate を削除する
コンパイラは nightly feature が許可された状態でビルドされるため、そこでもその feature の使用が見つかることがあります。これらも削除する必要があります。
#![feature(once_cell)]
#![feature(never_type)]
-#![feature(total_cmp)]
#![feature(trusted_step)]
#![feature(try_blocks)]
安定化 PR チェックリスト
feature を安定化するには、次の手順に従ってください。
- 安定化する feature のトラッキング issue に安定化レポートを作成します。
- (任意)部分的な安定化の場合は、安定化する issue のサブセットについて、新しい部分安定化 PR を作成します。
- @rust-lang/libs-api のメンバーに依頼して、トラッキング issue で FCP を開始してもらい、FCP が完了するまで待ちます(
disposition-merge付き)。 #[unstable(...)]を#[stable(since = "CURRENT_RUSTC_VERSION")]に変更します。ここでのCURRENT_RUSTC_VERSIONは文字どおりの意味であり、明記されたバージョン番号に置き換えるものではありません。- この API のテストまたは doc-test から
#![feature(...)]を削除します。その feature がコンパイラまたはツールで使用されている場合は、そこからも削除します。 - 該当する場合は、
#[rustc_const_unstable(...)]を#[rustc_const_stable(since = "CURRENT_RUSTC_VERSION")]に変更します。 rust-lang/rustに対して PR を開きます。- 適切なラベルを追加します:
@rustbot modify labels: +T-libs-api。 - “Closes #XXXXX” を追加して、トラッキング issue にリンクします。
- 適切なラベルを追加します:
feature を安定化する例は、FCP 付きのトラッキング issue #81656 と、関連する 実装 PR #84642 で確認できます。
破壊的変更
破壊的変更は、可能な限り避けるべきです。 RFC 1105 は、何が破壊的変更を構成するかについての基礎を定めています。 破壊は、その実際の影響に基づいて許容可能かどうか判断される場合があり、 その影響は crater の実行によって概算できます。
影響がそれほど大きくない場合は、壊れたクレートのメンテナーを巻き込み、それらを修正する PR を送ることが有効な戦略になり得ます。
新しいトレイト実装による破壊的変更
標準ライブラリへの多くの PR では、すでに安定化されているトレイトに対して新しい impl が追加されています。 これは、利用側を実にさまざまな奇妙な形で壊す可能性があります。 以下は、標準ライブラリに加えられた変更だけからは明らかでない可能性がある、 新しいトレイト実装による破壊的変更の例です。
2つ目のジェネリック impl が導入されると推論が壊れる
Rust は推論時に、ジェネリックトレイトに対する impl が1つしかないという事実を利用します。 2つ目の impl によってそのジェネリックの型が曖昧になると、これは壊れます。 たとえば、次があるとします。
// `std` 内
impl From<&str> for Arc<str> { .. }
// 外部の `lib` 内
let b = Arc::from("a");
その後、次を追加します。
impl From<&str> for Arc<str> { .. }
+ impl From<&str> for Arc<String> { .. }
すると、
let b = Arc::from("a");
はもはやコンパイルされません。これは、以前は Box<T> の T を推論で特定することに依存していたためです。
この種の破壊的変更は許容される場合もありますが、crater 実行によって影響範囲を見積もるべきです。
新しい impl が導入されると Deref 型強制が壊れる
Rust は、引数が直接型チェックを通らない場合、有効なトレイト impl を見つけるために Deref 型強制を使用します。 これは impl が1つしかない場合にのみ発生するように見えるため、新しい impl を導入すると、Deref 型強制に依存している利用側が壊れる可能性があります。 たとえば、次があるとします。
// `std` 内
impl Add<&str> for String { .. }
impl Deref for String { type Target = str; .. }
// 外部の `lib` 内
let a = String::from("a");
let b = String::from("b");
let c = a + &b;
その後、次を追加します。
impl Add<&str> for String { .. }
+ impl Add<char> for String { .. }
すると、
let c = a + &b;
はもはやコンパイルされません。これは、&String を &str へ強制するために Deref を使用しようとしなくなるためです。
この種の破壊的変更は許容される場合もありますが、crater 実行によって影響範囲を見積もるべきです。
#[fundamental] 型
#[fundamental] 属性で注釈された型には、異なるコヒーレンス規則があります。
詳細は RFC 1023 を参照してください。
これには次が含まれます。
&T&mut TBox<T>Pin<T>
通常、新しいトレイト実装における破壊的変更の範囲は、推論と Deref 型強制に限定されます。
#[fundamental] 型に対する新しいトレイト実装は、下流の impl と重複し、他の種類の破壊的変更を引き起こす可能性があります。
プレリュードに対する破壊的変更
プレリュードに変更を加えると、すべての Rust コードに影響するため、破損を簡単に引き起こす可能性があります。 ほとんどの場合、プレリュード項目は名前探索における優先順位が最も低いため(glob インポートよりも低い)、影響は限定的です。しかし、これが機能しないケースが 2 つあります。
トレイト
新しいトレイトをプレリュードに追加すると、既存の型で新しいメソッドが利用可能になります。 同じ名前のメソッドが別のトレイトからも利用可能な場合、これによりユーザーコードで名前解決エラーが発生する可能性があります。
このため、TryFrom および TryInto は 2019 年に安定化されていたにもかかわらず、2021 エディションでのみプレリュードに追加されました。
マクロ
他の項目型とは異なり、rustc のマクロに対する名前解決は、そのマクロが不安定であっても、プレリュードのマクロに他のマクロより低い優先順位を与えることをサポートしていません。 一般的なルールとして、エディション境界を除き、プレリュードにマクロを追加することは避けてください。
この問題は、assert_matches! マクロ を取り込もうとした際に発生しました。
ドキュメントの破壊的変更
まず、安定性保証とは何かについて簡単に説明します。これは、 特定のケースでその項目が何をするかを正確に説明するドキュメント内の記述です。たとえば:
- 浮動小数点数に対する関数が
NaNをどのように扱うかを正確に示すこと。 - ソートメソッドに特定の実行時間の上限があると述べること。
そのため、ドキュメント変更が安定性保証を更新/追加/削除する場合は、 非常に慎重に扱う必要があり、 libs API チームの FCP を経る必要があります。
これは、こちらで行われているように
# Current Implementation セクションを追加することで回避できます。
#[must_use] を追加するタイミング
#[must_use] 属性は、型や関数を明示的に考慮しなかったり、その出力を明示的に考慮しなかったりすることが、ほぼ確実にバグである場合に適用できます。
例として、Result は #[must_use] です。なぜなら、それを考慮しないことは、呼び出し元がメソッドに失敗する可能性があることを認識していなかったことを示している可能性があるためです。
// `check_status` は失敗しないのでしょうか?それとも、その `Result` を確認し忘れたのでしょうか?
check_status();
saturating_add のような演算子も #[must_use] です。なぜなら、その出力を考慮しないことは、呼び出し元がそれらが左辺を変更しないことを認識していなかったことを示している可能性があるためです。
// 呼び出し元は、このメソッドが `a` を変更すると仮定しているかもしれません
a.saturating_add(b);
Iterator トレイトによって生成されるコンビネータは #[must_use] です。なぜなら、それらを使用しないことは、呼び出し元が Iterator が遅延評価され、消費しない限り実際には何もしないことを認識していなかったことを示している可能性があるためです。
// 呼び出し元は、`collect` や `count` などを呼び出さない限り、
// このコードが何もしないことを認識していないかもしれません。
v.iter().map(|x| println!("{}", x));
一方、thread::JoinHandle は #[must_use] ではありません。なぜなら、投げっぱなしの処理をスポーンすることは正当なパターンであり、呼び出し元にハンドルを明示的に無視することを強制するのは、バグの兆候というよりも迷惑になり得るためです。
thread::spawn(|| {
// このバックグラウンド処理は待機されません
});
レビュアー向け
#[must_use] によって呼び出し元が値を明示的に無視することになる、正当なユースケースがないか探してください。これらが一般的であれば、#[must_use] はおそらく適切ではありません。
#[must_use] 属性は警告を生成するだけなので、技術的にはいつでも導入できます。ただし、迷惑な警告が蓄積するのを避けるため、既存の型や関数に新しい #[must_use] 属性を追加する前に、@rust-lang/libs に意見を求めてください。
特殊化の使用
特殊化は現在不安定です。その進捗はこちらで追跡できます。
私たちは特殊化に過度に依存することを避け、その使用を特定の実装の最適化に限定するようにしています。これらの特殊化された最適化では、公開メソッド自体を特殊化するのではなく、正しい実装を見つけるためにプライベートなトレイトを使用します。外部の呼び出し元に対するメソッドのディスパッチ方法を変更する特殊化の使用は、慎重に検討するべきです。
標準ライブラリで特殊化を使用する方法の例として、&[T] から Rc<[T]> を作成するケースを考えます。
impl<T: Clone> From<&[T]> for Rc<[T]> {
#[inline]
fn from(v: &[T]) -> Rc<[T]> {
unsafe { Self::from_iter_exact(v.iter().cloned(), v.len()) }
}
}
T: Copy の場合に最適化された実装があると便利です。
impl<T: Copy> From<&[T]> for Rc<[T]> {
#[inline]
fn from(v: &[T]) -> Rc<[T]> {
unsafe { Self::copy_from_slice(v) }
}
}
残念ながら、通常はこれら両方の impl を持つことはできません。重複してしまうためです。ここで、プライベートな特殊化を使用して内部的に正しい実装を選択できます。この場合、実装を切り替える RcFromSlice というトレイトを使用します。
impl<T: Clone> From<&[T]> for Rc<[T]> {
#[inline]
fn from(v: &[T]) -> Rc<[T]> {
<Self as RcFromSlice<T>>::from_slice(v)
}
}
/// `From<&[T]>` に使用される特殊化トレイト。
trait RcFromSlice<T> {
fn from_slice(slice: &[T]) -> Self;
}
impl<T: Clone> RcFromSlice<T> for Rc<[T]> {
#[inline]
default fn from_slice(v: &[T]) -> Self {
unsafe { Self::from_iter_exact(v.iter().cloned(), v.len()) }
}
}
impl<T: Copy> RcFromSlice<T> for Rc<[T]> {
#[inline]
fn from_slice(v: &[T]) -> Self {
unsafe { Self::copy_from_slice(v) }
}
}
min_specialization feature を使用した特殊化のみを使用するべきです。完全な specialization feature は不健全であることが知られています。
特殊化属性
デフォルト実装には現れないトレイト境界を、特殊化する実装で許可するために使用できる不安定な属性が 2 つあります。
rustc_specialization_trait は、トレイトの実装を「常に適用可能」なものに制限します。rustc_specialization_trait で注釈されたトレイトの実装は不安定なので、標準ライブラリからエクスポートされる stable なトレイトには使用するべきではありません。Sized は例外であり、すでに impl ブロックでは実装できないため、この属性を持つことができます。
注: rustc_specialization_trait は誤ったモノモーフィゼーションを防ぐだけであり、型が特殊化された型と特殊化されていない型の間で強制変換されることを防ぎません。これは、特殊化を一貫して適用しなければならない場合に重要になることがあります。詳細については rust-lang/rust#85863 を参照してください。
rustc_unsafe_specialization_marker は、関連項目を持たないトレイトに基づく特殊化を許可します。この属性が unsafe であるのは、特殊化の際にトレイトの実装から得られるライフタイム制約が考慮されないためです。次の例は rustc_unsafe_specialization_marker の制限を示しています。特殊化された実装は、'static ライフタイムを持つものだけでなく、すべての共有参照型に対して使用されます。このため、rustc_unsafe_specialization_marker の新しい使用は避けるべきです。
#[rustc_unsafe_specialization_marker]
trait StaticRef {}
impl<T> StaticRef for &'static T {}
trait DoThing: Sized {
fn do_thing(self);
}
impl<T> DoThing for T {
default fn do_thing(self) {
// 遅い実装
}
}
impl<T: StaticRef> DoThing for T {
fn do_thing(self) {
// 高速な実装
}
}
rustc_unsafe_specialization_marker は、Copy、FusedIterator、Eq など、std からエクスポートされるマーカートレイトに基づく既存の特殊化を許可するために存在します。
#[inline] を使うべき場合
インライン化は、実行速度の向上の可能性、コンパイル時間、コードサイズの間のトレードオフです。これについては、hashbrown クレートへのこの PR でいくつか議論されています。そのスレッドから引用します。
#[inline]は、単なるインライン化のヒントとは大きく異なります。前述したように、#[inline]が行うことに相当するものは C++ にはありません。デバッグモードでは、rustc は基本的に#[inline]を無視し、まるで書かれていないかのように扱います。リリースモードでは、コンパイラーはデフォルトで、#[inline]関数を参照しているすべてのコード生成ユニットに対してその関数をコード生成し、さらにinlinehintも追加します。つまり、16 個の CGU があり、そのすべてがあるアイテムを参照している場合、そのすべてにアイテムの実装全体がインライン化されるということです。
#[inline] を追加できる場合:
- public で、小さく、ジェネリックではない関数。
#[inline] は必要ないはずの場合:
- スコープ内に何らかのジェネリクスがあるメソッド。
- デフォルト実装を持たないトレイト上のメソッド。
#[inline] はいつでも後から導入できるため、迷った場合は単に削除してかまいません。
#[inline(always)] については?
#[inline(always)] が必要になることはほとんどありません。限られた場所で使用される private なヘルパーメソッドや、自明な演算子では有益な場合があります。この属性はマイクロベンチマークによって正当化されるべきです。
レビュー担当者向け
#[inline] はいつでも後から追加できるため、それが適切かどうかについて少しでも議論がある場合は、まずアノテーションを削除して判断を先送りしてかまいません。
ドキュメントエイリアスのポリシー
Rust のドキュメントでは、#[doc(alias = "name")] という構文を使用して、任意の宣言(関数、型、定数など)にエイリアスを追加できます。私たちは、人々が探しているものを見つけやすくするためにドキュメントエイリアスを使用しつつ、それらのエイリアスを保守しやすく価値の高いものにしておきたいと考えています。このポリシーでは、ドキュメントエイリアスを追加するケースと、それらのエイリアスを省くケースについて概説します。
- 人々がその用語をドキュメント検索で検索するかもしれないと合理的に期待できなければなりません。Rust のドキュメントが提供するのは名前検索であり、全文検索ではありません。そのため、人々がもっともらしい名前を検索する可能性はあると考えていますが、より一般的なドキュメント検索については Web 検索エンジンを使うと想定しています。
- 関連事項: 人々が現在、自分のよく知っている任意の言語に由来する言語固有の名前を Rust のドキュメントで検索しているとは考えておらず、またそれを新しいドキュメント検索機能として追加したくもありません。あなたのお気に入りの言語に基づくエイリアスは追加しないでください。そのような対応関係は、別個のガイドやリファレンスに置くべきです。一方で、人々が Rust に存在すると合理的に期待できる関数(たとえばシステム関数や C ライブラリ関数)について、Rust ではその関数が何と呼ばれているのかを知ろうとして、その Rust 名を探す可能性はあると考えています。
- 提案されたエイリアスは、その宣言に対して私たちがもっともらしく使っていた可能性のある名前でなければなりません。たとえば、
create_dirに対するmkdir、remove_dirに対するrmdir、count_onesに対するpopcntとpopcount、modeに対するumaskなどです。これは、誰かがその名前を検索し、それを見つけられると期待する(「Rust ではmkdirを何と呼んだのか」)という合理的な期待につながります。 - エイリアスには、エイリアス化された名前の正確な類似物である明らかな単一の対象がなければなりません。同じエイリアスを複数の宣言に追加することはありません。(同じ関数の
const版と非const版は問題ありません。)また、多少似ているだけ、または関連しているだけの関数にエイリアスを追加することもありません。 - エイリアスは、既存の宣言の実際の名前と衝突してはなりません。
- stdarch の特別なケースとして、正確なアセンブリ命令名から対応する組み込み関数へのエイリアスは、他の名前と衝突しない限り歓迎されます。
Safety コメント
Rust コンパイラや標準ライブラリでは unsafe ブロックの使用がしばしば必要になりますが、これはルールなしに行われるわけではありません。各 unsafe ブロックには、そのブロックがなぜ安全なのか、どの不変条件が使用され、尊重されなければならないのかを説明する SAFETY: コメントがあるべきです。以下は標準ライブラリから取ったいくつかの例です。
unsafe 要素の内部
この例は、unsafe 関数が # Safety セクションのドキュメントを使用して要件を呼び出し元に伝えつつ、呼び出し元には要求されない追加の不変条件を内部で必要とする方法を示しています。ちなみに、clippy には # Safety セクション用の lint があります。
/// ミュータブルな文字列スライスをミュータブルなバイトスライスに変換します。
///
/// # Safety
///
/// 呼び出し元は、借用が終了し、基になる `str` が使用される前に、
/// スライスの内容が有効な UTF-8 であることを保証しなければなりません。
///
/// 内容が有効な UTF-8 ではない `str` の使用は未定義動作です。
///
/// ...
pub unsafe fn as_bytes_mut(&mut self) -> &mut [u8] {
// SAFETY: `&str` から `&[u8]` へのキャストは安全です。なぜなら `str` は
// `&[u8]` と同じレイアウトを持つためです(この保証を行えるのは libstd のみです)。
// ポインターのデリファレンスは安全です。なぜなら、書き込みに対して有効であることが
// 保証されているミュータブル参照に由来するためです。
unsafe { &mut *(self as *mut str as *mut [u8]) }
}
この例は関数に関するものですが、同じ原則はたとえば Send や Sync のような unsafe trait にも適用されます。ただし、それらのドキュメント全体がなぜ unsafe であるかに関するものなので、# Safety セクションはありません。
Rust 標準ライブラリでは unsafe_op_in_unsafe_fn が有効になっているため、unsafe 関数内の各 unsafe 操作は unsafe ブロックで囲まれなければならないことに注意してください。これにより、そのような関数のレビューや、その unsafe な部分の文書化が容易になります。
safe 要素の内部
safe な要素の内部では、SAFETY: コメントは、適切に構築された型と値以外について、呼び出し元に依存してはなりません(つまり、あなたの関数がアラインメントされていない参照や null の参照を受け取った場合、それが失敗するなら呼び出し元の責任であり、あなたの責任ではありません)。
safe な要素内の SAFETY コメントは、unsafe ブロックの前に行われるチェックや型の不変条件に依存することが多いです。たとえば、NonZeroU8 による除算では、除算前に 0 かどうかをチェックしないような場合です。
pub fn split_at(&self, mid: usize) -> (&str, &str) {
// is_char_boundary は、インデックスが [0, .len()] の範囲内にあることをチェックします
if self.is_char_boundary(mid) {
// SAFETY: `mid` が文字境界上にあることを確認済みです。
unsafe { (self.get_unchecked(0..mid), self.get_unchecked(mid..self.len())) }
} else {
slice_error_fail(self, 0, mid)
}
}
ターゲット固有コードのレビュー
ターゲット固有コードをレビューする際には、対象となるターゲットの tier に 応じて、レビュアーには異なる水準の精査が求められます。
tier 1 ターゲットについては、レビュアーはコードの完全なレビューを行うべきです。 基本的には、そのコードをプラットフォーム固有ではないものとして扱ってください。
tier 2 および tier 3 ターゲットについては、レビュアーはそのコードが以下を満たすことを確認すべきです。
- そのようなターゲットの 1 つ以上にのみ影響すること(すなわち、本当にターゲット固有であること)
- 新たなライセンス上の危険を持ち込まないこと(例: ライセンスヘッダーなど)
- ターゲットメンテナー1によって提案されているか、少なくとも 1 人の ターゲットメンテナーに ping して +1 を受け取っていること。メンテナーがいない場合は、 作者に信頼性があるか、および/または何らかの形でそのターゲットと関係があるかを確認してください (例: 元のコードを作成した、そのターゲットを保守する企業で働いている、など)。
このレビューには、正確性やコード品質の確認は含まれないことに注意してください。 tier 2 および tier 3 ターゲットの詳細なレビューを行うためのレビュー帯域や専門知識が不足しています。
-
ターゲットメンテナーは、ほとんどのターゲットについて platform support ドキュメントに記載されています。 ↩
標準ライブラリが拡張トレイトを使用する理由(cfg でガードされた項目ではなく)
標準ライブラリでよく見られるパターンは、ターゲット固有のメソッドを、
オブジェクト自体に直接 cfg でガードされたメソッドとして提供するのではなく、
拡張トレイトに入れることです。たとえば、
std::os::unix::prelude
にある多数の拡張トレイトは、標準型に UNIX 固有のメソッドを提供します。
標準ライブラリは、その代わりに、#[cfg(unix)] でガードしたうえで、
これらのメソッドを標準型に直接提供することもできるでしょう。しかし、そうしておらず、
cfg でガードされたメソッドを追加する PR はしばしば却下されます。
これらのメソッドを拡張トレイト経由で提供すると、コードはメソッドにアクセスするために、
それらの拡張トレイトを明示的に使用することを強制されます。これは事実上、
そのコードがターゲット固有の機能に依存しているかどうかを宣言することを要求します。
それは、そのコードがターゲット固有であるためか、あるいは異なるターゲット向けに適切に
cfg でガードされたコードを持っているためです。これらの拡張トレイトがなければ、
コードはターゲット固有の機能をより簡単に「偶然」使用してしまう可能性があります。
Rust が、ターゲット固有の機能にアクセスする前に、コードが自身の移植性、または移植性がないことを 明示的に宣言するのを支援する、より優れた仕組みを開発した場合、 このポリシーは将来変更される可能性があります。
Drop と #[may_dangle]
Drop を手動で実装するジェネリックな Type<T> は、T に #[may_dangle] 属性が適切かどうかを検討すべきです。Nomicon には、#[may_dangle] が何であるかについての詳細があります。
ジェネリックな Type<T> に、T の drop も伴う可能性がある手動の drop 実装がある場合、dropck はそれを知る必要があります。Type<T> による T の所有権が、ManuallyDrop<T>、*mut T、MaybeUninit<T> のようにそれ自体では T を drop しない型を通じて表現されている場合、Type<T> には T が drop される可能性があることを dropck に伝えるための PhantomData<T> フィールドも必要です。標準ライブラリ内で内部の Unique<T> ポインター型を使用している型には、PhantomData<T> マーカーフィールドは必要ありません。それは Unique<T> によって処理されます。
これが実際にどのように問題になり得るかの例として、次のような OptionCell<T> を考えてみましょう。
struct OptionCell<T> {
is_init: bool,
value: MaybeUninit<T>,
}
impl<T> Drop for OptionCell<T> {
fn drop(&mut self) {
if self.is_init {
// Safety: `is_init` が true の場合、`value` は完全に初期化されていることが保証されます。
// Safety: セルは drop されている最中なので、再びアクセスされることはありません。
unsafe { self.value.assume_init_drop() };
}
}
}
PhantomData<T> マーカーフィールドを持たないこの OptionCell<T> に #[may_dangle] 属性を追加すると、OptionCell<T> より厳密には長く生存しない T に対して健全性の穴が生じ、それらが自身の Drop 実装内で、drop された後にアクセスされる可能性がありました。#[may_dangle] を正しく適用するには、PhantomData<T> フィールドも必要でした。
struct OptionCell<T> {
is_init: bool,
value: MaybeUninit<T>,
+ _marker: PhantomData<T>,
}
- impl<T> Drop for OptionCell<T> {
+ unsafe impl<#[may_dangle] T> Drop for OptionCell<T> {
レビュアー向け
手動の Drop 実装がある場合は、#[may_dangle] が適切かどうかを検討してください。適切である場合は、Unique<T> を通じて、または直接フィールドとして、PhantomData<T> も存在することを確認してください。
ジェネリクスとunsafe
unsafeコードと相互作用するジェネリック型には注意してください。ジェネリック型が、その契約を指定するunsafeトレイトによって境界付けられていない限り、ジェネリック型の結果が信頼できる、または正しいとは期待できません。
これがよく問題になる場所の1つが、RangeBoundsトレイトです。RangeBoundsの実装から与えられる開始境界と終了境界は、共有参照を介して動作するため同じままである、と仮定するかもしれません。しかし、必ずしもそうとは限らず、敵対的な実装は呼び出しの間に境界を変更する可能性があります。
struct EvilRange(Cell<bool>);
impl RangeBounds<usize> for EvilRange {
fn start_bound(&self) -> Bound<&usize> {
Bound::Included(if self.0.get() {
&1
} else {
self.0.set(true);
&0
})
}
fn end_bound(&self) -> Bound<&usize> {
Bound::Unbounded
}
}
これは過去に、境界が同じままであることをアサートせずに境界に基づいて安全性の仮定を行うコードで問題を引き起こしました。
unsafeと相互作用するためにジェネリック型を使用するコードは、まずそれらを既知の型に変換し、それからジェネリックの代わりにその型を扱うようにするべきです。RangeBoundsの例では、これは具象的なRange、または(Bound, Bound)のタプルに変換することを意味するかもしれません。
レビュー担当者向け
unsafeブロックも含むジェネリック関数に注意し、それらのジェネリクスの敵対的な実装がどのように安全性を侵害し得るかを検討してください。
ライブラリチーム
Rust 標準ライブラリと公式の rust-lang クレートは、
ライブラリチームの責任範囲です。
ライブラリチームは、ライブラリがメンテナンスされ、
PR がレビューされ、issue が適時に処理されるようにします。
ただし、それはチームメンバーがすべての作業を自分たちで行うという意味ではありません。
多くのチームメンバーやその他のコントリビューターがこの作業に関わっており、
チームの主な役割は、その作業を導き、可能にすることです。
ライブラリ API チーム
標準ライブラリのメンテナンスと発展において非常に重要な側面は、その安定性です。 他のクレートとは異なり、後方互換性のない変更のために、ときどき新しいメジャーバージョンをリリースすることはできません。 標準ライブラリのすべてのバージョンは、Rust 1.0 以降のすべての過去のバージョンと semver 互換です。
つまり、公開インターフェイスへの追加や変更には非常に慎重でなければなりません。 必要であれば非推奨にすることはできますが、 項目を削除したりシグネチャを変更したりすることは、ほとんどの場合選択肢になりません。 その結果、標準ライブラリへの追加を安定化する際には、私たちは非常に慎重になります。 いったん何かが安定化されると、基本的には永遠にそれを抱え続けることになります。
安定性を守り、後で後悔するようなものを追加してしまうことを防ぐために、 公開 API に特化して注力するチームがあります。 ライブラリの追加や変更に関するすべての RFC と安定化は FCP プロセスを経ます。 その中で、ライブラリ API チームのメンバーはその変更を承認するよう求められます。
このチームのメンバーは、必ずしも標準ライブラリの実装の詳細に精通しているわけではありませんが、 API 設計の経験があり、破壊的変更の詳細と、それをどのように回避するかを理解しています。
ライブラリコントリビューター
上記の 2 つのチームに加えて、ライブラリコントリビューターも存在します。 これは、標準ライブラリへの変更に定期的に貢献したりレビューしたりする人々で構成される、 やや緩やかに定義されたチームです。
これらのコントリビューターの多くは、特定の専門分野を持っています。 たとえば、特定のデータ構造や特定のオペレーティングシステムなどです。
チームメンバーシップ
ライブラリチームは、自チームおよびライブラリコントリビューターの潜在的な新メンバーについて非公開で議論し、 すべてのメンバーとモデレーションチームがその追加候補に同意した後に招待します。
詳細は メンバーシップ を参照してください。
r+ 権限
ライブラリチーム、ライブラリ API チーム、ライブラリコントリビューターのすべてのメンバーは、 PR を承認する権限を持ち、この権限を注意深く扱うことが期待されています。 詳細は レビュー を参照してください。
high-five ローテーション
チームメンバーの一部は「high-five ローテーション」に含まれています。 これは、high-five bot が新しい PR に割り当てるレビュアーを選ぶためのリストです。
いずれかのチームのメンバーであることによって、このリストに含まれることが期待されるわけではありません。 ただし、このリストのメンバーは、3 つのライブラリチームの少なくとも 1 つに所属しているべきです。 詳細は レビュー を参照してください。
ミーティング
現在、ライブラリチームとライブラリAPIチームはいずれも、週に1回、1時間のミーティングを行っています。 どちらのミーティングもデフォルトではメンバー以外にも公開されていますが、議題によっては(一部が)非公開になる場合があります。
ミーティングは Jitsi を通じたビデオ通話として開催されますが、誰でもビデオなし、あるいは音声なしでも参加できます。 テキストでミーティングの議論に参加したい場合は、Jitsi のチャット機能を通じて参加できます。
ミーティングとその議題は、Zulip の #t-libs/meetings チャンネルで告知されます。
議題は fully-automatic-rust-libs-team-triage-meeting-agenda-generator によって生成されます。
これには、I-nominated や S-waiting-on-team のタグが付けられたものなど、関連するすべての issue と PR が含まれます。
ミーティングで議論してほしい特定のトピックがある場合は、libs-team リポジトリで issue を開き、
I-nominated と T-libs または T-libs-api のマークを付けてください。あるいは、Zulip チャンネルにメッセージを残すだけでも構いません。
ライブラリワーキンググループのものを含むすべてのミーティングは、私たちの Google カレンダーで確認できます。
メンバーシップ
ライブラリコントリビューター
ライブラリコントリビューターのメンバーシップは、通常のコントリビューターが一定期間にわたっていくつもの重要な貢献を行い、 どの変更が受け入れ可能かについて適切な判断力を持つことを示した場合に、 ライブラリチームによって提供されます。
ライブラリチームとライブラリAPIチーム
ライブラリチームとライブラリAPIチームは、それぞれ自分たちのメンバーを選びますが、 新メンバーはライブラリコントリビューターまたは別の Rust チームの出身であり、 関連するライブラリ作業にすでに関わっていることが期待されます。
プロセス
すべての場合において、新メンバーを追加するプロセスは次のとおりです。
- ライブラリ(API)チームのメンバーが、非公開の Zulip チャンネルでコントリビューターの追加を提案します。
この提案には以下が含まれます。
- その人が取り組んできたこと、どのように貢献してきたかについての短い説明。
- その人が自分の考えを明確に伝えた事例の具体例をいくつか。
- その人が何が受け入れ可能な変更で、何がそうではないかを理解していることを示す具体例をいくつか。
重要な貢献をしているものの、通常は PR に大きな調整を加える必要がある人は、素晴らしい外部コントリビューターかもしれませんが、 他の貢献を判断することが期待されるレビュー権限を持つメンバーシップには、まだ適していないかもしれません。
- すべてのチームメンバーに意見を求めます。どのチームメンバーからも異議があってはなりません。
- 異議はチーム全体と共有されることが理想ですが、チームリードまたはモデレーションチームと非公開で共有される場合もあります。
- 異議には、ステップ 1 で説明された期待事項 (または行動規範)に沿わない振る舞いを示す例が含まれていることが理想です。
- チームリードはモデレーションチームに連絡し、何らかの異議を把握しているか尋ねます。
- チームメンバーとモデレーションチームが同意した場合にのみ、新しいコントリビューターが参加に招待されます。
- 新しいコントリビューターも同意した場合、
teamリポジトリにその人を追加する PR が送られます。 - 新しいコントリビューターの短い紹介を含むブログ記事が Internals Blog に公開されます。 この投稿の内容は、ステップ 1 のメールで挙げられたポイントの一部に基づくことができます。 内容は公開前に、まず新しいコントリビューターに確認されます。
レビュー
Library Team、Library API Team、および Library Contributors のすべてのメンバーには「r+ 権限」があります。
つまり、PR を承認し、@bors に対して
それをテストして Rust nightly にマージするよう指示する能力です。
PR をレビューすることにしたなら、ありがとうございます! ただし、以下の点に留意してください。
- 誰に割り当てられているかにかかわらず、どの PR でもレビューすることはいつでも歓迎されます。
ただし、以下の場合を除き、PR を承認しないでください。
- 他の誰も先にレビューしたいと思っていないと確信できる場合。チームの他の誰かの方がレビュー担当者として適任だと思う場合は、遠慮なくその人に再割り当てしてください。
- コードのその部分に自信がある場合。
- 破損やパフォーマンスの回帰を引き起こさないと確信できる場合。
- 変更について完了した FCP がある場合を除き、ドキュメントで行っている安定性に関する約束を含め、公開 API を変更しない場合。
- 不安定 API の変更/追加については、設計が小さく、変更に議論の余地がない場合、RFC プロセスを省略することが許容される場合があります。
そのような変更には必ず
@rust-lang/libs-apiを関与させてください。
- 不安定 API の変更/追加については、設計が小さく、変更に議論の余地がない場合、RFC プロセスを省略することが許容される場合があります。
そのような変更には必ず
- レビュー時は常に礼儀正しくしてください。あなたは Rust プロジェクトの代表者であるため、行動規範に関しては期待以上の対応をすることが期待されています。
レビューの詳細については、https://forge.rust-lang.org/compiler/reviews.html を参照してください。
High-five ローテーション
チームの一部のメンバーは「high-five ローテーション」に参加しています。 これは、high-five bot が新しい PR の割り当て先レビュアーを選ぶリストです。
いずれかのチームのメンバーであることは、このリストに載ることを期待されるものではありません。 ただし、このリストのメンバーは、3 つのライブラリチームの少なくとも 1 つに所属している必要があります。
bot があなたに PR を割り当てたものの、それをレビューする時間や専門知識がない場合は、
遠慮なく他の誰かに再割り当てしてください。
high-five ローテーションから選ばれた別のランダムな人に割り当てるには、
r? rust-lang/libs を使用してください。
長期間にわたってレビューを行えない状況になった場合は、 リストから自分を(一時的に)削除するのがよいかもしれません。 リストに自分を追加または削除するには、 triagebot 設定ファイルを変更する PR を送ってください。
ロールアップ
ライブラリ PR では、ロールアップ(@bors r+ rollup)は多くの場合問題ありません。
特に、新しい不安定な追加のみの場合や、ドキュメントのみに触れる場合です。
パフォーマンスに影響する PR はロールアップすべきではありません(@bors rollup=never)。
微妙なプラットフォーム固有の変更を含む PR も、ロールアップの候補として適していない場合があります。
ロールアップすべきタイミングの詳細については、
ロールアップのガイドラインを参照してください。