プログラミング
Rustのderiveはしばしばinlineを暗示する
Rust's derive often implies inline (yossarian.net)
要約
Rustの`#[derive(...)]`マクロは、デバッグ、表示、クローンなどのコアトレイトの実装を生成する際に、デフォルトで`#[inline]`属性を付与することが多いです。これは通常、これらの派生実装が小さく、インライン化の恩恵を受けるため望ましいですが、エラー階層のような複雑な構造では、過剰なインライン化がバイナリサイズを大幅に増加させる可能性があります。この問題に対処するために、`#[inline(never)]`を持つカスタムマクロを使用するアプローチが紹介されています。
全文翻訳
すべてのTIL ホームページ ブログ 2026-10-03 rust
Rustでは、コアトレイト(Debug、Display、Cloneなど)を実装する最も一般的な方法の1つは、`#[derive(...)]`を使用することです。例:
`#[derive(Debug)] struct Widgets { foo: u32, bar: usize, }`
最近まで知らなかったのですが、Rustは現在、これらの派生の一部として`#[inline]`を発行しています。これは保証されているわけではないようですが、リファレンスの例で暗示されており、マクロを展開すると確認できます。上記の例を使用すると、プレイグラウンドで`#[derive(Debug)]`を展開すると次のようになります:
`struct Widgets { foo: u32, bar: usize, }`
`#[automatically_derived]`
`impl ::core::fmt::Debug for Widgets {`
` #[inline]`
` fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {`
` ::core::fmt::Formatter::debug_struct_field2_finish(f, "Widgets", "foo", &self.foo, "bar", &&self.bar)`
` }`
`}`
これはほとんどの場合、私たちが望むものです:`#[inline]`は単なるヒントであり、典型的な派生Debug、Cloneなどはインライン化の恩恵を受けます(それらはしばしば些細なため)。しかし、常にそうとは限りません!
次のようなエラー階層を想像してみてください1:
`#[derive(Debug)] struct ErrorA { lots: String, of: String, chunky: String, fields: String, within: String, this: String, r#type: String, }`
`#[derive(Debug)] struct ErrorB { inner: ErrorA, }`
`#[derive(Debug)] struct ErrorC { inner: ErrorB, }`
`#[derive(Debug)] enum Errors { A(ErrorA), B(ErrorB), C(ErrorC), }`
これは次を生成します:
`struct ErrorA { lots: String, of: String, chunky: String, fields: String, within: String, this: String, r#type: String, }`
`#[automatically_derived]`
`impl ::core::fmt::Debug for ErrorA {`
` #[inline]`
` fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {`
` let names: &'static _ = &["lots", "of", "chunky", "fields", "within", "this", "type"];`
` let values: &[&dyn ::core::fmt::Debug] = &[&self.lots, &self.of, &self.chunky, &self.fields, &self.within, &self.this, &&self.r#type];`
` ::core::fmt::Formatter::debug_struct_fields_finish(f, "ErrorA", names, values)`
` }`
`}`
`#[automatically_derived]`
`impl ::core::default::Default for ErrorA {`
` #[inline]`
` fn default() -> Self {`
` Self {`
` lots: ::core::default::Default::default(),`
` of: ::core::default::Default::default(),`
` chunky: ::core::default::Default::default(),`
` fields: ::core::default::Default::default(),`
` within: ::core::default::Default::default(),`
` this: ::core::default::Default::default(),`
` r#type: ::core::default::Default::default(),`
` }`
` }`
`}`
`struct ErrorB { inner: ErrorA, }`
`#[automatically_derived]`
`impl ::core::fmt::Debug for ErrorB {`
` #[inline]`
` fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {`
` ::core::fmt::Formatter::debug_struct_field1_finish(f, "ErrorB", "inner", &&self.inner)`
` }`
`}`
`struct ErrorC { inner: ErrorB, }`
`#[automatically_derived]`
`impl ::core::fmt::Debug for ErrorC {`
` #[inline]`
` fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {`
` ::core::fmt::Formatter::debug_struct_field1_finish(f, "ErrorC", "inner", &&self.inner)`
` }`
`}`
`enum Errors { A(ErrorA), B(ErrorB), C(ErrorC), }`
`#[automatically_derived]`
`impl ::core::fmt::Debug for Errors {`
` #[inline]`
` fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {`
` match self {`
` Self::A(__self_0) => ::core::fmt::Formatter::debug_tuple_field1_finish(f, "A", &__self_0),`
` Self::B(__self_0) => ::core::fmt::Formatter::debug_tuple_field1_finish(f, "B", &__self_0),`
` Self::C(__self_0) => ::core::fmt::Formatter::debug_tuple_field1_finish(f, "C", &__self_0),`
` }`
` }`
`}`
これは、ErrorsのDebug実装の各呼び出しでインライン化される可能性のある多くのコードであり、デバッグやトレースロギングなどで繰り返し発生する可能性があります。実際、Rustバイナリの総サイズのかなりの部分を占める可能性があります:uvのバイナリサイズを約160KB削減できたことがわかりました。これは、rustcが特定のDebug実装のインライン化にサイズや回数の制限を適用していないように見えたため、私にとって2つのレベルで驚きでした。しかし、多くのプログラムではこれが正しい決定だと思います!
この階層は劇的に単純化されています:実際のRustアプリケーションは、しばしば非自明な数のフィールドを持つ深くネストされたエラー列挙型を持っています。↩
これは、`derive(Debug)`のように動作する独自のprocマクロを追加することで行いました。ただし、`#[inline(never)]`を使用します。↩