Add a fast path for lowering trivial consts

This commit is contained in:
Ben Kimock
2025-10-23 12:27:56 -04:00
parent 4ddbb60512
commit 775da711c6
16 changed files with 191 additions and 83 deletions

View File

@@ -7,7 +7,7 @@ use rustc_hir::def::DefKind;
use rustc_middle::mir::interpret::{AllocId, ErrorHandled, InterpErrorInfo, ReportedErrorInfo};
use rustc_middle::mir::{self, ConstAlloc, ConstValue};
use rustc_middle::query::TyCtxtAt;
use rustc_middle::ty::layout::HasTypingEnv;
use rustc_middle::ty::layout::{HasTypingEnv, TyAndLayout};
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_middle::{bug, throw_inval};
@@ -24,13 +24,11 @@ use crate::interpret::{
};
use crate::{CTRL_C_RECEIVED, errors};
// Returns a pointer to where the result lives
#[instrument(level = "trace", skip(ecx, body))]
fn eval_body_using_ecx<'tcx, R: InterpretationResult<'tcx>>(
fn setup_for_eval<'tcx>(
ecx: &mut CompileTimeInterpCx<'tcx>,
cid: GlobalId<'tcx>,
body: &'tcx mir::Body<'tcx>,
) -> InterpResult<'tcx, R> {
layout: TyAndLayout<'tcx>,
) -> InterpResult<'tcx, (InternKind, MPlaceTy<'tcx>)> {
let tcx = *ecx.tcx;
assert!(
cid.promoted.is_some()
@@ -46,7 +44,6 @@ fn eval_body_using_ecx<'tcx, R: InterpretationResult<'tcx>>(
"Unexpected DefKind: {:?}",
ecx.tcx.def_kind(cid.instance.def_id())
);
let layout = ecx.layout_of(body.bound_return_ty().instantiate(tcx, cid.instance.args))?;
assert!(layout.is_sized());
let intern_kind = if cid.promoted.is_some() {
@@ -58,12 +55,25 @@ fn eval_body_using_ecx<'tcx, R: InterpretationResult<'tcx>>(
}
};
let ret = if let InternKind::Static(_) = intern_kind {
create_static_alloc(ecx, cid.instance.def_id().expect_local(), layout)?
let return_place = if let InternKind::Static(_) = intern_kind {
create_static_alloc(ecx, cid.instance.def_id().expect_local(), layout)
} else {
ecx.allocate(layout, MemoryKind::Stack)?
ecx.allocate(layout, MemoryKind::Stack)
};
return_place.map(|ret| (intern_kind, ret))
}
#[instrument(level = "trace", skip(ecx, body))]
fn eval_body_using_ecx<'tcx, R: InterpretationResult<'tcx>>(
ecx: &mut CompileTimeInterpCx<'tcx>,
cid: GlobalId<'tcx>,
body: &'tcx mir::Body<'tcx>,
) -> InterpResult<'tcx, R> {
let tcx = *ecx.tcx;
let layout = ecx.layout_of(body.bound_return_ty().instantiate(tcx, cid.instance.args))?;
let (intern_kind, ret) = setup_for_eval(ecx, cid, layout)?;
trace!(
"eval_body_using_ecx: pushing stack frame for global: {}{}",
with_no_trimmed_paths!(ecx.tcx.def_path_str(cid.instance.def_id())),
@@ -87,6 +97,31 @@ fn eval_body_using_ecx<'tcx, R: InterpretationResult<'tcx>>(
}
}
intern_and_validate(ecx, cid, intern_kind, ret)
}
#[instrument(level = "trace", skip(ecx))]
fn eval_trivial_const_using_ecx<'tcx, R: InterpretationResult<'tcx>>(
ecx: &mut CompileTimeInterpCx<'tcx>,
cid: GlobalId<'tcx>,
val: ConstValue,
ty: Ty<'tcx>,
) -> InterpResult<'tcx, R> {
let layout = ecx.layout_of(ty)?;
let (intern_kind, return_place) = setup_for_eval(ecx, cid, layout)?;
let opty = ecx.const_val_to_op(val, ty, Some(layout))?;
ecx.copy_op(&opty, &return_place)?;
intern_and_validate(ecx, cid, intern_kind, return_place)
}
fn intern_and_validate<'tcx, R: InterpretationResult<'tcx>>(
ecx: &mut CompileTimeInterpCx<'tcx>,
cid: GlobalId<'tcx>,
intern_kind: InternKind,
ret: MPlaceTy<'tcx>,
) -> InterpResult<'tcx, R> {
// Intern the result
let intern_result = intern_const_alloc_recursive(ecx, intern_kind, &ret);
@@ -292,6 +327,9 @@ pub fn eval_to_const_value_raw_provider<'tcx>(
tcx: TyCtxt<'tcx>,
key: ty::PseudoCanonicalInput<'tcx, GlobalId<'tcx>>,
) -> ::rustc_middle::mir::interpret::EvalToConstValueResult<'tcx> {
if let Some((value, _ty)) = tcx.trivial_const(key.value.instance.def_id()) {
return Ok(value);
}
tcx.eval_to_allocation_raw(key).map(|val| turn_into_const_value(tcx, val, key))
}
@@ -368,10 +406,14 @@ fn eval_in_interpreter<'tcx, R: InterpretationResult<'tcx>>(
// so we have to reject reading mutable global memory.
CompileTimeMachine::new(CanAccessMutGlobal::from(is_static), CheckAlignment::Error),
);
let res = ecx.load_mir(cid.instance.def, cid.promoted);
res.and_then(|body| eval_body_using_ecx(&mut ecx, cid, body))
.report_err()
.map_err(|error| report_eval_error(&ecx, cid, error))
let result = if let Some((value, ty)) = tcx.trivial_const(def) {
eval_trivial_const_using_ecx(&mut ecx, cid, value, ty)
} else {
ecx.load_mir(cid.instance.def, cid.promoted)
.and_then(|body| eval_body_using_ecx(&mut ecx, cid, body))
};
result.report_err().map_err(|error| report_eval_error(&ecx, cid, error))
}
#[inline(always)]

View File

@@ -1088,9 +1088,15 @@ fn run_required_analyses(tcx: TyCtxt<'_>) {
sess.time("MIR_borrow_checking", || {
tcx.par_hir_body_owners(|def_id| {
if !tcx.is_typeck_child(def_id.to_def_id()) {
let not_typeck_child = !tcx.is_typeck_child(def_id.to_def_id());
if not_typeck_child {
// Child unsafety and borrowck happens together with the parent
tcx.ensure_ok().check_unsafety(def_id);
}
if tcx.is_trivial_const(def_id) {
return;
}
if not_typeck_child {
tcx.ensure_ok().mir_borrowck(def_id);
tcx.ensure_ok().check_transmutes(def_id);
}
@@ -1198,7 +1204,9 @@ fn analysis(tcx: TyCtxt<'_>, (): ()) {
if tcx.sess.opts.unstable_opts.validate_mir {
sess.time("ensuring_final_MIR_is_computable", || {
tcx.par_hir_body_owners(|def_id| {
tcx.instance_mir(ty::InstanceKind::Item(def_id.into()));
if !tcx.is_trivial_const(def_id) {
tcx.instance_mir(ty::InstanceKind::Item(def_id.into()));
}
});
});
}

View File

@@ -240,6 +240,7 @@ provide! { tcx, def_id, other, cdata,
thir_abstract_const => { table }
optimized_mir => { table }
mir_for_ctfe => { table }
trivial_const => { table }
closure_saved_names_of_captured_variables => { table }
mir_coroutine_witnesses => { table }
promoted_mir => { table }

View File

@@ -1791,8 +1791,15 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
record!(self.tables.mir_coroutine_witnesses[def_id.to_def_id()] <- witnesses);
}
}
let mut is_trivial = false;
if encode_const {
record!(self.tables.mir_for_ctfe[def_id.to_def_id()] <- tcx.mir_for_ctfe(def_id));
if let Some((val, ty)) = tcx.trivial_const(def_id) {
is_trivial = true;
record!(self.tables.trivial_const[def_id.to_def_id()] <- (val, ty));
} else {
is_trivial = false;
record!(self.tables.mir_for_ctfe[def_id.to_def_id()] <- tcx.mir_for_ctfe(def_id));
}
// FIXME(generic_const_exprs): this feels wrong to have in `encode_mir`
let abstract_const = tcx.thir_abstract_const(def_id);
@@ -1810,7 +1817,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
}
}
}
record!(self.tables.promoted_mir[def_id.to_def_id()] <- tcx.promoted_mir(def_id));
if !is_trivial {
record!(self.tables.promoted_mir[def_id.to_def_id()] <- tcx.promoted_mir(def_id));
}
if self.tcx.is_coroutine(def_id.to_def_id())
&& let Some(witnesses) = tcx.mir_coroutine_witnesses(def_id)
@@ -2234,6 +2243,9 @@ fn prefetch_mir(tcx: TyCtxt<'_>) {
let reachable_set = tcx.reachable_set(());
par_for_each_in(tcx.mir_keys(()), |&&def_id| {
if tcx.is_trivial_const(def_id) {
return;
}
let (encode_const, encode_opt) = should_encode_mir(tcx, reachable_set, def_id);
if encode_const {

View File

@@ -29,6 +29,7 @@ use rustc_middle::middle::exported_symbols::{ExportedSymbol, SymbolExportInfo};
use rustc_middle::middle::lib_features::FeatureStability;
use rustc_middle::middle::resolve_bound_vars::ObjectLifetimeDefault;
use rustc_middle::mir;
use rustc_middle::mir::ConstValue;
use rustc_middle::ty::fast_reject::SimplifiedType;
use rustc_middle::ty::{self, Ty, TyCtxt, UnusedGenericParams};
use rustc_middle::util::Providers;
@@ -426,6 +427,7 @@ define_tables! {
object_lifetime_default: Table<DefIndex, LazyValue<ObjectLifetimeDefault>>,
optimized_mir: Table<DefIndex, LazyValue<mir::Body<'static>>>,
mir_for_ctfe: Table<DefIndex, LazyValue<mir::Body<'static>>>,
trivial_const: Table<DefIndex, LazyValue<(ConstValue, Ty<'static>)>>,
closure_saved_names_of_captured_variables: Table<DefIndex, LazyValue<IndexVec<FieldIdx, Symbol>>>,
mir_coroutine_witnesses: Table<DefIndex, LazyValue<mir::CoroutineLayout<'static>>>,
promoted_mir: Table<DefIndex, LazyValue<IndexVec<mir::Promoted, mir::Body<'static>>>>,

View File

@@ -102,6 +102,7 @@ trivially_parameterized_over_tcx! {
rustc_middle::middle::lib_features::FeatureStability,
rustc_middle::middle::resolve_bound_vars::ObjectLifetimeDefault,
rustc_middle::mir::ConstQualifs,
rustc_middle::mir::ConstValue,
rustc_middle::ty::AnonConstKind,
rustc_middle::ty::AssocContainer,
rustc_middle::ty::AsyncDestructor,

View File

@@ -16,6 +16,7 @@ where
let mirs = def_ids
.iter()
.filter(|def_id| !tcx.is_trivial_const(*def_id))
.flat_map(|def_id| {
if tcx.is_const_fn(*def_id) {
vec![tcx.optimized_mir(*def_id), tcx.mir_for_ctfe(*def_id)]

View File

@@ -353,8 +353,16 @@ pub fn write_mir_pretty<'tcx>(
// are shared between mir_for_ctfe and optimized_mir
writer.write_mir_fn(tcx.mir_for_ctfe(def_id), w)?;
} else {
let instance_mir = tcx.instance_mir(ty::InstanceKind::Item(def_id));
render_body(w, instance_mir)?;
if let Some((val, ty)) = tcx.trivial_const(def_id) {
ty::print::with_forced_impl_filename_line! {
// see notes on #41697 elsewhere
write!(w, "const {}", tcx.def_path_str(def_id))?
}
writeln!(w, ": {} = const {};", ty, Const::Val(val, ty))?;
} else {
let instance_mir = tcx.instance_mir(ty::InstanceKind::Item(def_id));
render_body(w, instance_mir)?;
}
}
}
Ok(())

View File

@@ -160,6 +160,10 @@ impl EraseType for Result<mir::ConstValue, mir::interpret::ErrorHandled> {
type Result = [u8; size_of::<Result<mir::ConstValue, mir::interpret::ErrorHandled>>()];
}
impl EraseType for Option<(mir::ConstValue, Ty<'_>)> {
type Result = [u8; size_of::<Option<(mir::ConstValue, Ty<'_>)>>()];
}
impl EraseType for EvalToValTreeResult<'_> {
type Result = [u8; size_of::<EvalToValTreeResult<'static>>()];
}

View File

@@ -2719,6 +2719,12 @@ rustc_queries! {
separate_provide_extern
}
query trivial_const(def_id: DefId) -> Option<(mir::ConstValue, Ty<'tcx>)> {
desc { |tcx| "checking if `{}` is a trivial const", tcx.def_path_str(def_id) }
cache_on_disk_if { def_id.is_local() }
separate_provide_extern
}
/// Checks for the nearest `#[sanitize(xyz = "off")]` or
/// `#[sanitize(xyz = "on")]` on this def and any enclosing defs, up to the
/// crate root.

View File

@@ -3546,6 +3546,13 @@ impl<'tcx> TyCtxt<'tcx> {
self.get_diagnostic_attr(def_id, sym::do_not_recommend).is_some()
}
pub fn is_trivial_const<P>(self, def_id: P) -> bool
where
P: IntoQueryParam<DefId>,
{
self.trivial_const(def_id).is_some()
}
/// Whether this def is one of the special bin crate entrypoint functions that must have a
/// monomorphization and also not be internalized in the bin crate.
pub fn is_entrypoint(self, def_id: DefId) -> bool {

View File

@@ -23,11 +23,11 @@ use rustc_hir::def::{CtorKind, DefKind};
use rustc_hir::def_id::LocalDefId;
use rustc_index::IndexVec;
use rustc_middle::mir::{
AnalysisPhase, Body, CallSource, ClearCrossCrate, ConstOperand, ConstQualifs, LocalDecl,
MirPhase, Operand, Place, ProjectionElem, Promoted, RuntimePhase, Rvalue, START_BLOCK,
SourceInfo, Statement, StatementKind, TerminatorKind,
AnalysisPhase, Body, CallSource, ClearCrossCrate, ConstOperand, ConstQualifs, ConstValue,
LocalDecl, MirPhase, Operand, Place, ProjectionElem, Promoted, RETURN_PLACE, RuntimePhase,
Rvalue, START_BLOCK, SourceInfo, Statement, StatementKind, TerminatorKind,
};
use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt};
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt};
use rustc_middle::util::Providers;
use rustc_middle::{bug, query, span_bug};
use rustc_mir_build::builder::build_mir;
@@ -226,6 +226,7 @@ pub fn provide(providers: &mut Providers) {
promoted_mir,
deduced_param_attrs: deduce_param_attrs::deduced_param_attrs,
coroutine_by_move_body_def_id: coroutine::coroutine_by_move_body_def_id,
trivial_const: trivial_const_provider,
..providers.queries
};
}
@@ -376,9 +377,71 @@ fn mir_const_qualif(tcx: TyCtxt<'_>, def: LocalDefId) -> ConstQualifs {
validator.qualifs_in_return_place()
}
fn def_kind_compatible_with_trivial_mir<'tcx>(tcx: TyCtxt<'tcx>, def: LocalDefId) -> bool {
// Static and InlineConst are the obvious additions, but
// * Statics need additional type-checking to taint `static A: _ = 0;`, currently we'd ICE.
// * The MIR for InlineConst is used by the borrow checker, and not easy to skip over.
matches!(tcx.def_kind(def), DefKind::AssocConst | DefKind::Const | DefKind::AnonConst)
}
fn trivial_const_provider<'tcx>(
tcx: TyCtxt<'tcx>,
def: LocalDefId,
) -> Option<(ConstValue, Ty<'tcx>)> {
if def_kind_compatible_with_trivial_mir(tcx, def) {
trivial_const(&tcx.mir_built(def).borrow())
} else {
None
}
}
fn trivial_const<'tcx>(body: &Body<'tcx>) -> Option<(ConstValue, Ty<'tcx>)> {
if body.has_opaque_types() {
return None;
}
if body.basic_blocks.len() != 1 {
return None;
}
let block = &body.basic_blocks[START_BLOCK];
if block.statements.len() != 1 {
return None;
}
if block.terminator().kind != TerminatorKind::Return {
return None;
}
let StatementKind::Assign(box (place, rvalue)) = &block.statements[0].kind else {
return None;
};
if *place != Place::from(RETURN_PLACE) {
return None;
}
if let Rvalue::Use(Operand::Constant(c)) = rvalue {
if let rustc_middle::mir::Const::Val(v, ty) = c.const_ {
return Some((v, ty));
}
}
return None;
}
fn mir_built(tcx: TyCtxt<'_>, def: LocalDefId) -> &Steal<Body<'_>> {
let mut body = build_mir(tcx, def);
// Identifying trivial consts based on their mir_built is easy, but a little wasteful.
// Trying to push this logic earlier in the compiler and never even produce the Body would
// probably improve compile time.
if def_kind_compatible_with_trivial_mir(tcx, def) && trivial_const(&body).is_some() {
let body = tcx.alloc_steal_mir(body);
pass_manager::dump_mir_for_phase_change(tcx, &body.borrow());
return body;
}
pass_manager::dump_mir_for_phase_change(tcx, &body);
pm::run_passes(
@@ -409,6 +472,8 @@ fn mir_promoted(
tcx: TyCtxt<'_>,
def: LocalDefId,
) -> (&Steal<Body<'_>>, &Steal<IndexVec<Promoted, Body<'_>>>) {
debug_assert!(!tcx.is_trivial_const(def), "Tried to get mir_promoted of a trivial const");
// Ensure that we compute the `mir_const_qualif` for constants at
// this point, before we steal the mir-const result.
// Also this means promotion can rely on all const checks having been done.
@@ -436,6 +501,9 @@ fn mir_promoted(
tcx.ensure_done().coroutine_by_move_body_def_id(def);
}
// the `trivial_const` query uses mir_built, so make sure it is run.
tcx.ensure_done().trivial_const(def);
let mut body = tcx.mir_built(def).steal();
if let Some(error_reported) = const_qualifs.tainted_by_errors {
body.tainted_by_errors = Some(error_reported);
@@ -463,6 +531,7 @@ fn mir_promoted(
/// Compute the MIR that is used during CTFE (and thus has no optimizations run on it)
fn mir_for_ctfe(tcx: TyCtxt<'_>, def_id: LocalDefId) -> &Body<'_> {
debug_assert!(!tcx.is_trivial_const(def_id), "Tried to get mir_for_ctfe of a trivial const");
tcx.arena.alloc(inner_mir_for_ctfe(tcx, def_id))
}

View File

@@ -92,7 +92,8 @@ impl<'tcx, B: Bridge> CompilerCtxt<'tcx, B> {
} else {
false
};
!must_override && self.tcx.is_mir_available(def_id)
// FIXME: A good reason to make is_mir_available or mir_keys change behavior
!must_override && self.tcx.is_mir_available(def_id) && !self.tcx.is_trivial_const(def_id)
}
fn filter_fn_def(&self, def_id: DefId) -> Option<DefId> {

View File

@@ -14,7 +14,7 @@ impl Tr for str {
type Arr = [u8; 8];
#[cfg(cfail)]
type Arr = [u8; Self::C];
//[cfail]~^ ERROR cycle detected when caching mir
//[cfail]~^ ERROR cycle detected when
}
fn main() {}

View File

@@ -397,13 +397,6 @@ fn operands(_1: u8) -> () {
_89 = core::panicking::assert_failed::<usize, usize>(move _90, move _91, move _93, move _95) -> unwind unreachable;
}
}
fn operands::{constant#0}() -> usize {
let mut _0: usize;
bb0: {
_0 = 10_usize;
return;
}
}
fn more_operands() -> [Ctors; 3] {
let mut _0: [Ctors; 3];
let _1: Dummy;
@@ -447,13 +440,6 @@ fn more_operands() -> [Ctors; 3] {
return;
}
}
fn more_operands::{constant#0}() -> usize {
let mut _0: usize;
bb0: {
_0 = 3_usize;
return;
}
}
fn closures(_1: bool, _2: bool) -> {closure@$DIR/operands.rs:47:5: 47:19} {
let mut _0: {closure@$DIR/operands.rs:47:5: 47:19};
debug x => _1;

View File

@@ -17,27 +17,7 @@ note: ...which requires const-evaluating + checking `accept0::{constant#0}`...
|
LL | fn accept0<T: Trait>(_: Container<{ T::make() }>) {}
| ^^^^^^^^^^^^^
note: ...which requires caching mir of `accept0::{constant#0}` for CTFE...
--> $DIR/unsatisfied-const-trait-bound.rs:29:35
|
LL | fn accept0<T: Trait>(_: Container<{ T::make() }>) {}
| ^^^^^^^^^^^^^
note: ...which requires elaborating drops for `accept0::{constant#0}`...
--> $DIR/unsatisfied-const-trait-bound.rs:29:35
|
LL | fn accept0<T: Trait>(_: Container<{ T::make() }>) {}
| ^^^^^^^^^^^^^
note: ...which requires borrow-checking `accept0::{constant#0}`...
--> $DIR/unsatisfied-const-trait-bound.rs:29:35
|
LL | fn accept0<T: Trait>(_: Container<{ T::make() }>) {}
| ^^^^^^^^^^^^^
note: ...which requires promoting constants in MIR for `accept0::{constant#0}`...
--> $DIR/unsatisfied-const-trait-bound.rs:29:35
|
LL | fn accept0<T: Trait>(_: Container<{ T::make() }>) {}
| ^^^^^^^^^^^^^
note: ...which requires const checking `accept0::{constant#0}`...
note: ...which requires checking if `accept0::{constant#0}` is a trivial const...
--> $DIR/unsatisfied-const-trait-bound.rs:29:35
|
LL | fn accept0<T: Trait>(_: Container<{ T::make() }>) {}
@@ -70,32 +50,12 @@ LL | fn accept0<T: Trait>(_: Container<{ T::make() }>) {}
| ^^^^^^^^^^^^^
= note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information
error[E0391]: cycle detected when caching mir of `accept1::{constant#0}` for CTFE
error[E0391]: cycle detected when checking if `accept1::{constant#0}` is a trivial const
--> $DIR/unsatisfied-const-trait-bound.rs:33:49
|
LL | const fn accept1<T: [const] Trait>(_: Container<{ T::make() }>) {}
| ^^^^^^^^^^^^^
|
note: ...which requires elaborating drops for `accept1::{constant#0}`...
--> $DIR/unsatisfied-const-trait-bound.rs:33:49
|
LL | const fn accept1<T: [const] Trait>(_: Container<{ T::make() }>) {}
| ^^^^^^^^^^^^^
note: ...which requires borrow-checking `accept1::{constant#0}`...
--> $DIR/unsatisfied-const-trait-bound.rs:33:49
|
LL | const fn accept1<T: [const] Trait>(_: Container<{ T::make() }>) {}
| ^^^^^^^^^^^^^
note: ...which requires promoting constants in MIR for `accept1::{constant#0}`...
--> $DIR/unsatisfied-const-trait-bound.rs:33:49
|
LL | const fn accept1<T: [const] Trait>(_: Container<{ T::make() }>) {}
| ^^^^^^^^^^^^^
note: ...which requires const checking `accept1::{constant#0}`...
--> $DIR/unsatisfied-const-trait-bound.rs:33:49
|
LL | const fn accept1<T: [const] Trait>(_: Container<{ T::make() }>) {}
| ^^^^^^^^^^^^^
note: ...which requires building MIR for `accept1::{constant#0}`...
--> $DIR/unsatisfied-const-trait-bound.rs:33:49
|
@@ -126,7 +86,7 @@ note: ...which requires const-evaluating + checking `accept1::{constant#0}`...
|
LL | const fn accept1<T: [const] Trait>(_: Container<{ T::make() }>) {}
| ^^^^^^^^^^^^^
= note: ...which again requires caching mir of `accept1::{constant#0}` for CTFE, completing the cycle
= note: ...which again requires checking if `accept1::{constant#0}` is a trivial const, completing the cycle
note: cycle used when const-evaluating + checking `accept1::{constant#0}`
--> $DIR/unsatisfied-const-trait-bound.rs:33:49
|