現在、 format_args!
は、実行時にArgumentV1::new(&runtime_data, Debug::fmt)
( {:?}
)などを使用し、実行時に引数ごとに1つだけではなく&runtime_data
)。 。
allow_internal_unsafe
と#44240を使用すると、(たとえばDebug::fmt
) fn
ポインターを(右辺値でプロモートされた) 'static
データに配置できます。残りのハードルは、ランタイムデータのタイプを推測します。
つまり、 Debug::fmt
は実際には<_ as Debug>::fmt
あり、 ArgumentV1::new
の署名がそれらを一緒に入力しているため、 _
は現在推測されています。 それらが分離している場合は、何か新しいものが必要です。
HList
パターンを使用することを提案します( struct HCons<H, T>(H, T); struct HNil;
-したがって、タイプA
、 B
、およびC
3つの要素の場合HCons<A, HCons<B, HCons<C, HNil>>>
)、 #[repr(C)]
を使用すると、配列のレイアウトと一致する決定論的なレイアウト、つまり次の2つが得られます。
&'static HCons<fn(&A), HCons<fn(&B), HCons<fn(&C), HNil>>>
&'static [unsafe fn(*const Opaque); 3]
同じ表現を持ち、後者はスライスにサイズ変更することができます。 HList
から配列(そしてスライス)へのこの変換は、安全で右辺値でプロモートされたHCons
上で実行できます。これは、 fn
ポインターを移動するために必要な要件です。 'static
データにすべて。
推論のために、型を一致させるためにいくつかの関数呼び出しを挿入するだけです。たとえば、 B
を推論するには、 fmt::unify_fn_with_data((list.1).0, &b)
を実行できます。これにより、 B
がtypeof b
。
HList
のフォーマッターとHList
のランタイム参照を組み合わせて型を統一する、完全に安全な「ビルダー」インターフェースを使用する方が実際には簡単かもしれませんが、少し心配です。すべてのトレイトディスパッチによるコンパイル時間-いずれの場合も、影響を測定する必要があります。
@rustbotクレーム
推論のために、我々は単に推論するなど、種類を一致させるためにいくつかの関数呼び出しを挿入することができます
B
私たちは何ができるfmt::unify_fn_with_data((list.1).0, &b)
になるだろうこれ、B
にtypeof b
。
私がそこで何を考えていたのかわからない、それはそれよりはるかに簡単なはずです!
struct ArgMetadata<T: ?Sized> {
// Only `unsafe` because of the later cast we do from `T` to `Opaque`.
fmt: unsafe fn(&T, &mut Formatter<'_>) -> Result,
// ... flags, constant string fragments, etc.
}
// TODO: maybe name this something else to emphasize repr(C)?
#[repr(C)]
struct HCons<T, Rest>(T, Rest);
// This would have to be in a "sealed module" to make it impossible to implement on more types.
trait MetadataFor<D> {
const LEN: usize;
}
impl MetadataFor<()> for () {
const LEN: usize = 0;
}
impl<'a, T: ?Sized, D, M> MetadataFor<HCons<&'a T, D>> for HCons<ArgMetadata<T>, M>
where M: MetadataFor<D>
{
const LEN: usize = M::LEN;
}
impl<'a> Arguments<'a> {
fn new<M, D>(meta: &'a M, data: &'a D) -> Self
where M: MetadataFor<D>
{
Self {
meta: unsafe { &*(meta as *const _ as *const [ArgMetadata<Opaque>; M::LEN]) },
data: unsafe { &*(data as *const _ as *const [&Opaque; M::LEN]) },
}
}
}
つまり、2つのHList
「並列」に構築します。1つは完全に一定のメタデータを使用し、もう1つはランタイムデータへの参照を使用します。その後、すべての型推論はwhere
から取得できます。 fmt::Arguments::new
句、codegen cruftはゼロです!
編集:@ m-ou-seは、なぜ私が最初に明示的な推論のトリックを採用したのかを私に思い出させなければなりませんでした:ランダムアクセス引数:失望:
(多分十分なのconstジェネリックABの使用を我々は持っている可能性がD: IndexHList<i, Output = T>
それは多くの努力です)
fmt::Arguments
新しい実装があります。これは、文字列部分とフォーマットオプション(存在する場合)の両方を含む新しい形式の静的メタデータを使用することにより、サイズが2ポインターのみです。 (したがって、レジスタペアに収まるようになりました。これは非常に便利です。)また、 @ eddybが3年前にこの号ですでに提案したように、引数ごとにスタックに必要なポインタは2つではなく1つだけです。 ^^( @ eddybが昨日指摘するまで、この問題を完全に見逃していました。^^ ')
まだ残っているのは、 format_args!()
を更新して、代わりにこの新しい型を生成することです。これにより、オブジェクトポインターと関数ポインター(現在ArgumentV1
一緒にあります)を関数ポインターとして分割する問題が発生します。代わりに '静的メタデータに入れる必要があります。 この号の提案は、それを行うための良い方法のように見えます。 すぐにそれを実装しようとします。 パフォーマンスの実行を楽しみにしています:)
最も参考になるコメント
私がそこで何を考えていたのかわからない、それはそれよりはるかに簡単なはずです!
つまり、2つの
HList
「並列」に構築します。1つは完全に一定のメタデータを使用し、もう1つはランタイムデータへの参照を使用します。その後、すべての型推論はwhere
から取得できます。fmt::Arguments::new
句、codegen cruftはゼロです!編集:@ m-ou-seは、なぜ私が最初に明示的な推論のトリックを採用したのかを私に思い出させなければなりませんでした:ランダムアクセス引数:失望:
(多分十分なのconstジェネリック
ABの使用を我々は持っている可能性がD: IndexHList<i, Output = T>
それは多くの努力です)