Skip to content

Constify Fn* traits #143640

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions compiler/rustc_hir_typeck/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,6 @@ hir_typeck_cast_unknown_pointer = cannot cast {$to ->
hir_typeck_const_continue_bad_label =
`#[const_continue]` must break to a labeled block that participates in a `#[loop_match]`

hir_typeck_const_select_must_be_const = this argument must be a `const fn`
.help = consult the documentation on `const_eval_select` for more information

hir_typeck_const_select_must_be_fn = this argument must be a function item
.note = expected a function item, found {$ty}
.help = consult the documentation on `const_eval_select` for more information

hir_typeck_continue_labeled_block =
`continue` pointing to a labeled block
.label = labeled blocks cannot be `continue`'d
Expand Down
23 changes: 0 additions & 23 deletions compiler/rustc_hir_typeck/src/callee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -578,29 +578,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}

if let Some(def_id) = def_id
&& self.tcx.def_kind(def_id) == hir::def::DefKind::Fn
&& self.tcx.is_intrinsic(def_id, sym::const_eval_select)
{
let fn_sig = self.resolve_vars_if_possible(fn_sig);
for idx in 0..=1 {
let arg_ty = fn_sig.inputs()[idx + 1];
let span = arg_exprs.get(idx + 1).map_or(call_expr.span, |arg| arg.span);
// Check that second and third argument of `const_eval_select` must be `FnDef`, and additionally that
// the second argument must be `const fn`. The first argument must be a tuple, but this is already expressed
// in the function signature (`F: FnOnce<ARG>`), so I did not bother to add another check here.
//
// This check is here because there is currently no way to express a trait bound for `FnDef` types only.
if let ty::FnDef(def_id, _args) = *arg_ty.kind() {
if idx == 0 && !self.tcx.is_const_fn(def_id) {
self.dcx().emit_err(errors::ConstSelectMustBeConst { span });
}
} else {
self.dcx().emit_err(errors::ConstSelectMustBeFn { span, ty: arg_ty });
}
}
}

fn_sig.output()
}

Expand Down
18 changes: 0 additions & 18 deletions compiler/rustc_hir_typeck/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -605,24 +605,6 @@ impl Subdiagnostic for RemoveSemiForCoerce {
}
}

#[derive(Diagnostic)]
#[diag(hir_typeck_const_select_must_be_const)]
#[help]
pub(crate) struct ConstSelectMustBeConst {
#[primary_span]
pub span: Span,
}

#[derive(Diagnostic)]
#[diag(hir_typeck_const_select_must_be_fn)]
#[note]
#[help]
pub(crate) struct ConstSelectMustBeFn<'a> {
#[primary_span]
pub span: Span,
pub ty: Ty<'a>,
}

#[derive(Diagnostic)]
#[diag(hir_typeck_union_pat_multiple_fields)]
pub(crate) struct UnionPatMultipleFields {
Expand Down
6 changes: 5 additions & 1 deletion compiler/rustc_hir_typeck/src/upvar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// process any deferred resolutions.
let deferred_call_resolutions = self.remove_deferred_call_resolutions(closure_def_id);
for deferred_call_resolution in deferred_call_resolutions {
deferred_call_resolution.resolve(self);
deferred_call_resolution.resolve(&mut FnCtxt::new(
self,
self.param_env,
closure_def_id,
));
Comment on lines -532 to +536
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was necessary, as otherwise

const FOO: impl Fn() = || {
    let x = || ();
    x()
};

would have performed some checks in the outer closure's body as if it were const, causing the x() invocation to fail because x is not a const closure

}
}

Expand Down
51 changes: 50 additions & 1 deletion compiler/rustc_trait_selection/src/traits/effects.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use rustc_hir::{self as hir, LangItem};
use rustc_infer::infer::{BoundRegionConversionTime, DefineOpaqueTypes};
use rustc_infer::traits::{
ImplDerivedHostCause, ImplSource, Obligation, ObligationCauseCode, PredicateObligation,
ImplDerivedHostCause, ImplSource, Obligation, ObligationCause, ObligationCauseCode,
PredicateObligation,
};
use rustc_middle::span_bug;
use rustc_middle::traits::query::NoSolution;
Expand Down Expand Up @@ -303,6 +304,9 @@ fn evaluate_host_effect_from_builtin_impls<'tcx>(
) -> Result<ThinVec<PredicateObligation<'tcx>>, EvaluationFailure> {
match selcx.tcx().as_lang_item(obligation.predicate.def_id()) {
Some(LangItem::Destruct) => evaluate_host_effect_for_destruct_goal(selcx, obligation),
Some(LangItem::Fn | LangItem::FnMut | LangItem::FnOnce) => {
evaluate_host_effect_for_fn_goal(selcx, obligation)
}
_ => Err(EvaluationFailure::NoSolution),
}
}
Expand Down Expand Up @@ -398,6 +402,51 @@ fn evaluate_host_effect_for_destruct_goal<'tcx>(
.collect())
}

// NOTE: Keep this in sync with `extract_fn_def_from_const_callable` in the new solver.
fn evaluate_host_effect_for_fn_goal<'tcx>(
selcx: &mut SelectionContext<'_, 'tcx>,
obligation: &HostEffectObligation<'tcx>,
) -> Result<ThinVec<PredicateObligation<'tcx>>, EvaluationFailure> {
let tcx = selcx.tcx();
let self_ty = obligation.predicate.self_ty();

let (def, args) = match *self_ty.kind() {
ty::FnDef(def, args) => (def, args),

// We may support function pointers at some point in the future
ty::FnPtr(..) => return Err(EvaluationFailure::NoSolution),

// Closures could implement `[const] Fn`,
// but they don't really need to right now.
ty::Closure(..) | ty::CoroutineClosure(_, _) => {
return Err(EvaluationFailure::NoSolution);
}

// Everything else needs explicit impls or cannot have an impl
_ => return Err(EvaluationFailure::NoSolution),
};

match tcx.constness(def) {
hir::Constness::Const => Ok(tcx
.const_conditions(def)
.instantiate(tcx, args)
.into_iter()
.map(|(c, span)| {
let code = ObligationCauseCode::WhereClause(def, span);
let cause =
ObligationCause::new(obligation.cause.span, obligation.cause.body_id, code);
Obligation::new(
tcx,
cause,
obligation.param_env,
c.to_host_effect_clause(tcx, obligation.predicate.constness),
)
})
.collect()),
hir::Constness::NotConst => Err(EvaluationFailure::NoSolution),
}
}

fn evaluate_host_effect_from_selection_candidate<'tcx>(
selcx: &mut SelectionContext<'_, 'tcx>,
obligation: &HostEffectObligation<'tcx>,
Expand Down
2 changes: 1 addition & 1 deletion library/core/src/intrinsics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2279,7 +2279,7 @@ pub const fn const_eval_select<ARG: Tuple, F, G, RET>(
) -> RET
where
G: FnOnce<ARG, Output = RET>,
F: FnOnce<ARG, Output = RET>;
F: const FnOnce<ARG, Output = RET>;

/// A macro to make it easier to invoke const_eval_select. Use as follows:
/// ```rust,ignore (just a macro example)
Expand Down
34 changes: 21 additions & 13 deletions library/core/src/ops/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ use crate::marker::Tuple;
)]
#[fundamental] // so that regex can rely that `&str: !FnMut`
#[must_use = "closures are lazy and do nothing unless called"]
// FIXME(const_trait_impl) #[const_trait]
#[const_trait]
#[rustc_const_unstable(feature = "const_trait_impl", issue = "67792")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we have a proper lib feature and tracking issue here, rather than slapping everything into a 5-year-old issue that was created for an entirely different generation of the const trait RFC evolution?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lang feature is not very useful without the lib feature, requiring separate lib features for different traits is just going to require one to add many features when using any somewhat large feature.

We could pick a new tracking issue, but that also just feels like shuffling things around for no good reason.

I have no idea what the process will be for stabilizing any of these, a lot of them are entangled. We'll likely want to stabilize many traits as const together with stabilizing the lang feature to not cause weird ecosystem churn

pub trait Fn<Args: Tuple>: FnMut<Args> {
/// Performs the call operation.
#[unstable(feature = "fn_traits", issue = "29625")]
Expand Down Expand Up @@ -159,7 +160,8 @@ pub trait Fn<Args: Tuple>: FnMut<Args> {
)]
#[fundamental] // so that regex can rely that `&str: !FnMut`
#[must_use = "closures are lazy and do nothing unless called"]
// FIXME(const_trait_impl) #[const_trait]
#[const_trait]
#[rustc_const_unstable(feature = "const_trait_impl", issue = "67792")]
pub trait FnMut<Args: Tuple>: FnOnce<Args> {
/// Performs the call operation.
#[unstable(feature = "fn_traits", issue = "29625")]
Expand Down Expand Up @@ -238,7 +240,8 @@ pub trait FnMut<Args: Tuple>: FnOnce<Args> {
)]
#[fundamental] // so that regex can rely that `&str: !FnMut`
#[must_use = "closures are lazy and do nothing unless called"]
// FIXME(const_trait_impl) #[const_trait]
#[const_trait]
#[rustc_const_unstable(feature = "const_trait_impl", issue = "67792")]
pub trait FnOnce<Args: Tuple> {
/// The returned type after the call operator is used.
#[lang = "fn_once_output"]
Expand All @@ -254,29 +257,32 @@ mod impls {
use crate::marker::Tuple;

#[stable(feature = "rust1", since = "1.0.0")]
impl<A: Tuple, F: ?Sized> Fn<A> for &F
#[rustc_const_unstable(feature = "const_trait_impl", issue = "67792")]
impl<A: Tuple, F: ?Sized> const Fn<A> for &F
where
F: Fn<A>,
F: ~const Fn<A>,
{
extern "rust-call" fn call(&self, args: A) -> F::Output {
(**self).call(args)
}
}

#[stable(feature = "rust1", since = "1.0.0")]
impl<A: Tuple, F: ?Sized> FnMut<A> for &F
#[rustc_const_unstable(feature = "const_trait_impl", issue = "67792")]
impl<A: Tuple, F: ?Sized> const FnMut<A> for &F
where
F: Fn<A>,
F: ~const Fn<A>,
{
extern "rust-call" fn call_mut(&mut self, args: A) -> F::Output {
(**self).call(args)
}
}

#[stable(feature = "rust1", since = "1.0.0")]
impl<A: Tuple, F: ?Sized> FnOnce<A> for &F
#[rustc_const_unstable(feature = "const_trait_impl", issue = "67792")]
impl<A: Tuple, F: ?Sized> const FnOnce<A> for &F
where
F: Fn<A>,
F: ~const Fn<A>,
{
type Output = F::Output;

Expand All @@ -286,19 +292,21 @@ mod impls {
}

#[stable(feature = "rust1", since = "1.0.0")]
impl<A: Tuple, F: ?Sized> FnMut<A> for &mut F
#[rustc_const_unstable(feature = "const_trait_impl", issue = "67792")]
impl<A: Tuple, F: ?Sized> const FnMut<A> for &mut F
where
F: FnMut<A>,
F: ~const FnMut<A>,
{
extern "rust-call" fn call_mut(&mut self, args: A) -> F::Output {
(*self).call_mut(args)
}
}

#[stable(feature = "rust1", since = "1.0.0")]
impl<A: Tuple, F: ?Sized> FnOnce<A> for &mut F
#[rustc_const_unstable(feature = "const_trait_impl", issue = "67792")]
impl<A: Tuple, F: ?Sized> const FnOnce<A> for &mut F
where
F: FnMut<A>,
F: ~const FnMut<A>,
{
type Output = F::Output;
extern "rust-call" fn call_once(self, args: A) -> F::Output {
Expand Down
Loading
Loading