Skip to content

Tracking Issue for non-lifetime binders #108185

Description

@compiler-errors

This is a tracking issue for "non-lifetime binders" (rust-lang/types-team#81).
The feature gate for the issue is #![feature(non_lifetime_binders)].

About tracking issues

Tracking issues are used to record the overall progress of implementation.
They are also used as hubs connecting to other relevant issues, e.g., bugs or open design questions.
A tracking issue is however not meant for large scale discussion, questions, or bug reports about a feature.
Instead, open a dedicated issue for the specific matter and add the relevant feature gate label.

Steps

Unresolved Questions

None yet

Implementation history

Activity

  1. added
    C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFC
    on Feb 17, 2023
  2. compiler-errors commented on Mar 22, 2023

    @compiler-errors
    ContributorAuthor

    Having recently thoroughly reviewed the guidelines for experimenting with new feature gates (#109010 (comment) 😅), it dawned on me that that the T-types MCP that I did in rust-lang/types-team#81 may have been insufficient to justify landing experimental support for non-lifetime binders in the compiler...

    Would anyone from @rust-lang/lang like to chime in here? Not exactly sure if it warrants a T-lang second + kicking off an FCP if we're trying to follow procedure, or if it's not worth it a month after landing the PR adding support. In any case, I'm trying to at least retroactively do my due dilligence here (and will do so for feature gates I add in the future in the future, sorry about that again lol).

    Given the MCP from a T-types perspective, it's already confirmed that we on T-types would like to have non-lifetime binders support in the language for experimentation (especially given the pretty gnarly modifications to the trait solver that will be needed to get non-lifetime binders working fully), but if T-lang is not confident about this experimental feature (or would like an RFC), I guess I could un-land it as well. Let me know!

  3. nikomatsakis commented on Mar 22, 2023

    @nikomatsakis
    Contributor

    I'm happy to serve as a lang-team second here. I don't think we need to do the full MCP, but if anyone on @rust-lang/lang has concerns about the idea of supporting things like for<T>, please raise them now.

    Just for good measure, I'll nominate for lang team triage meeting.

    @rustbot labels +I-lang-nominated

  4. scottmcm commented on Mar 24, 2023

    @scottmcm
    Member

    To me, accepting GATs was a de-facto "yes, you can experiment with for<T>", since the parallels are so strong.

  5. nikomatsakis commented on Apr 4, 2023

    @nikomatsakis
    Contributor

    @rustbot labels -I-lang-nominated

  6. removed
    I-lang-nominatedNominated for discussion during a lang team meeting.
    on Apr 4, 2023
  7. rebenkoy commented on Jan 13, 2024

    @rebenkoy
  8. compiler-errors commented on Jan 13, 2024

    @compiler-errors
    ContributorAuthor

    @rebenkoy: Thanks, but no need to comment on this tracking issue -- opening a bug report is sufficient. The feature is marked as incomplete because non-lifetime binders still has bugs. That issue will be fixed in due time.

    warning: the feature `non_lifetime_binders` is incomplete and may not be safe to use and/or cause compiler crashes
     --> src/lib.rs:1:12
      |
    1 | #![feature(non_lifetime_binders)]
      |            ^^^^^^^^^^^^^^^^^^^^
    
  9. zvolin commented on Jan 17, 2024

    @zvolin

    I'm curious if that could work with const generics. I'm currently working with Multihash and MultihashDigest and it's often a pain to propagate the parameter all the time. Consider this example:

    trait ToArray<const N: usize> {
        fn to_array(&self) -> [u8; N];
    }
    
    struct Array<const N: usize>([u8; N]);
    
    impl<const N: usize> ToArray<N> for Array<N> {
        fn to_array(&self) -> [u8; N] {
            self.0
        }
    }
    
    struct Vec2(Vec<u8>);
    
    impl Vec2 {
        fn from_arr<const N: usize, A>(other: A) -> Self
        where
            A: ToArray<N>,
        {
            Self(other.to_array().to_vec())
        }
    }
    
    fn main() {
        let other = Array([0; 20]);
        Vec2::from_arr::<20, _>(other);
    }

    Specifying this 20 doesn't read nice. I'd love to be able to write it like so

    impl Vec2 {
        fn from_arr<A>(other: A) -> Self
        where
            A: for<const N: usize> ToArray<N>,
        {
            Self(other.to_array().to_vec())
        }
    }
    
    fn main() {
        let other = Array([0; 20]);
        Vec2::from_arr(other);
    }
  10. c3potheds commented on Jan 17, 2024

    @c3potheds
    impl Vec2 {
        fn from_arr<A>(other: A) -> Self
        where
            A: for<const N: usize> ToArray<N>,
        {
            Self(other.to_array().to_vec())
        }
    }
    
    fn main() {
        let other = Array([0; 20]);
        Vec2::from_arr(other);
    }

    I don't think that would do what you think. The bounds you put on A say "A can be turned into an array of any size", not "A can be turned into an array of some size".

    You'd want to use:

    impl Vec2 {
        fn from_arr<A, const N: usize>(other: A) -> Self
        where
            A: ToArray<N>,
        {
            Self(other.to_array().to_vec())
        }
    }

    I could imagine a FromArray<const N: usize> trait where a universal type would be useful, though:

    trait FromArray<T, const N: usize> {
        fn from_array(a: [T; N]) -> Self:
    }
    
    // Implement the trait for one specific size.
    impl<T> FromArray<T, 2> for (T, T) {
        fn from_array(a: [T; 2]) -> Self {
            (a[0], a[1])
        }
    }
    
    // Implement the trait for all sizes.
    impl<T, const N: usize> FromArray<T, N> for Vec<T> {
        fn from_array(a: [T; N]) -> Self {
            a.into_iter().collect()
        }
    }
    
    // Require something that implements the trait for all sizes.
    fn create_test_data<A>() -> [A; 3]
    where
        for <const N: usize> A: FromArray<i32, N>,
    {
        [
            A::from_array([0]),
            A::from_array([0, 1]),
            A::from_array([0, 1, 2]),
        ]
    }
  11. added
    T-typesRelevant to the types team, which will review and decide on the PR/issue.
    B-experimentalBlocker: In-tree experiment; RFC pending, not yet approved or unneeded (requires FCP to stabilize).
    B-unstableBlocker: Implemented in the nightly compiler and unstable.
    on Feb 28, 2024
  12. changed the title [-]Tracking Issue for Non-lifetime Binders[/-] [+]Tracking Issue for non-lifetime binders[/+] on May 29, 2024
  13. added a commit that references this issue on Mar 29, 2025
  14. newpavlov commented on Apr 21, 2025

    @newpavlov
    Contributor

    I would like to provide a motivating example for this feature. In RustCrypto we have algorithm implementations which use target feature detection at runtime (e.g. block ciphers). On top of these algorithms we build higher-level constructions (e.g. the CTR mode).

    Depending on available target features we change number of blocks which is processed n parallel (because for efficiency we rely on ILP). For efficient codegen (i.e. we want for runtime detection to happen only once when we use the CTR mode, not on each block cipher encryption) we would like to express something like this:

    pub trait BlockCipher {
        const BLOCK_SIZE: usize;
    
        fn encrypt_with_backend<F>(&self, f: F)
        where
            F: for<B: BlockCipherBackend<BLOCK_SIZE = Self::BLOCK_SIZE>> FnOnce(B);
    
        fn encrypt_blocks(&self, blocks: &mut [[u8; Self::BLOCK_SIZE]]) {
            self.encrypt_with_backend(|b: B| {
                let mut iter = blocks.array_chunks_mut::<B::PAR_BLOCKS>();
                for par_blocks in iter {
                    b.par_encrypt(par_blocks);
                }
                for block in iter.into_remainder() {
                    b.encrypt(block);
                }
            })
        }
    }
    
    pub trait BlockCipherBackend: Sized {
        const BLOCK_SIZE: usize;
        const PAR_BLOCKS: usize;
    
        fn encrypt(&self, block: &mut [u8; Self::BLOCK_SIZE]);
        fn par_encrypt(&self, block: &mut [[u8; Self::BLOCK_SIZE]; Self::PAR_BLOCKS]);
    }
    
    // user crate
    let cipher = Aes128::new(&key);
    cipher.encrypt_with_backend(|cipher_backend| {
        // CTR implementation using provided `cipher_backend`
    });

    Don't mind the reliance on generic_const_exprs in the example, I used it for simplicity. In practice we use typenum instead of const generics.

    The important thing to understand in this example is that backend is chosen not by the caller, but by BlockCipher implementation, potentially at runtime. In other words, we want to generate a separate monomorphized version of the closure for each potentially possible backend.

    Right now it's possible to emulate this by manually implementing "closure" (see the BlockClosure trait), but it's incredibly annoying and verbose.

    One piece which is missing in the current non_lifetime_binders implementation is an ability to specify bounds on a declared for type.

  15. Danvil commented on Jun 8, 2025

    @Danvil
    Contributor

    I would like to provide another motivating example for this feature:

    /// Treat right argument as constant: f(x) := f(x, b)
    pub struct LeftBivariate<'a, F, B>(pub &'a F, pub &'a B);
    
    impl<'a, F, B> LeftBivariate<'a, F, B> {
        pub fn new(f: &'a F, b: &'a B) -> Self {
            Self(f, b)
        }
    }
    
    impl<'a, F, K, X, B, GX> Gradient<K, X, GX> for LeftBivariate<'a, F, B>
    where
        F: for<GB> BivariateGradient<K, X, B, GX, GB>, // <<<<<
    {
        fn gradient(&self, x: &X) -> GX {
            self.0.gradient_a(x, self.1)
        }
    }
    

    With the following traits:

    pub trait Gradient<K, X, G> {
        /// ∇ f(x)
        fn gradient(&self, x: &X) -> G;
    }
    
    pub trait BivariateGradient<K, A, B, GA, GB> {
        /// ∇ₐ f(a, b)
        fn gradient_a(&self, a: &A, b: &B) -> GA;
    
        /// ∇ᵦ f(a, b)
        fn gradient_b(&self, a: &A, b: &B) -> GB;
    }
    

    Basically I want to implement a trait for some type under some constraints, while I don't care about some type parameters which are necessary to write down the constraints.

    The only solution I could find is to add GB as PhantomData to the definition of LeftBivariate.

    Another possible syntax to express this could be F: BivariateGradient<K, X, B, GX, _>, which also does not seem to be possible in Rust today.

  16. Keith-Cancel commented on Dec 30, 2025

    @Keith-Cancel
    Contributor

    While bounds are not allowed at the moment.

    pub trait Bar {}
    // Not currently supported, by this feature
    pub trait Foo: for<T: Bar> Into<T> {}

    While it is kinda clunky, one can always define a wrapper trait like this to get the needed bounds:

    #![feature(non_lifetime_binders)]
    pub trait Bar {}
    pub trait Foo: for<T> IntoBar<T> {}
    // Define a trait with your desired bounds
    pub trait IntoBar<T: Bar>: Into<T> {
        fn into_bar(self) -> T;
    }
    // You can even then define a blanket like this
    impl<T: Bar, S: Into<T>> IntoBar<T> for S {
        fn into_bar(self) -> T {
            self.into()
        }
    }

    Also regarding supporting bounds I think it might nice to have the following for a shorthand instead of the full For syntax:

    pub trait Bar {}
    // Just allow impl to be used here
    pub trait Foo: Into<impl Bar> {}
    // it would be equivalent to:
    pub trait Foo: for<T: Bar> Into<T> {}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    B-experimentalBlocker: In-tree experiment; RFC pending, not yet approved or unneeded (requires FCP to stabilize).B-unstableBlocker: Implemented in the nightly compiler and unstable.C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCF-non_lifetime_binders`#![feature(non_lifetime_binders)]`S-tracking-impl-incompleteStatus: The implementation is incomplete.T-typesRelevant to the types team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions