• Name: where effect bounds
  • Proposed by: @CaioOliveira793
  • Original proposal: None

Design

This syntax focus on being simple and recognizable rust code, with the possibility to incrementally extend the capabilities that keyewords generic may provide.

base (reference)


#![allow(unused)]
fn main() {
/// A trimmed-down version of the `std::Iterator` trait.
pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    fn size_hint(&self) -> (usize, Option<usize>);
}

/// An adaptation of `Iterator::find` to a free-function
pub fn find<I, T, P>(iter: &mut I, predicate: P) -> Option<T>
where
    I: Iterator<Item = T> + Sized,
    P: FnMut(&T) -> bool;
}

always async


#![allow(unused)]
fn main() {
pub trait Iterator<effect>
where
    effect: async
{
    type Item;

    // opt-in for a always async effect
    fn next(&mut self) -> Option<Self::Item>
    where
        effect: async;

    // the size_hint is left unchanged, since the effect is opt-in
    fn size_hint(&self) -> (usize, Option<usize>);
}

pub fn find<I, T, P, effect>(iter: &mut I, predicate: P) -> Option<T>
where
    effect: async,
    I: Iterator<Item = T> + Sized,
    <I as Iterator>::effect: async,
    P: FnMut(&T) -> bool,
    <P as FnMut>::effect: async;
}

maybe async


#![allow(unused)]
fn main() {
pub trait Iterator<effect>
where
    effect: ?async
{
    type Item;

    fn next(&mut self) -> Option<Self::Item>
    where
        effect: ?async;

    fn size_hint(&self) -> (usize, Option<usize>);
}

pub fn find<I, T, P, effect>(iter: &mut I, predicate: P) -> Option<T>
where
    effect: ?async,
    I: Iterator<Item = T> + Sized,
    <I as Iterator>::effect: ?async,
    P: FnMut(&T) -> bool,
    <P as FnMut>::effect: ?async;
}

generic over all modifier keywords


#![allow(unused)]
fn main() {
pub trait Iterator<effect>
where
    // LIMITATION: in order to be generic over all keywords the effect clause must specify all keywords available
    effect: ?async + ?const
{
    type Item;

    fn next(&mut self) -> Option<Self::Item>
    where
        effect: ?async + ?const;

    fn size_hint(&self) -> (usize, Option<usize>);
}

pub fn find<I, T, P, effect>(iter: &mut I, predicate: P) -> Option<T>
where
    effect: ?async + ?const,
    I: Iterator<Item = T> + Sized,
    <I as Iterator>::effect: ?async + ?const,
    P: FnMut(&T) -> bool,
    <P as FnMut>::effect: ?async + ?const;
}

Notes

Trait effect bounds

The syntax for specifying the effect of a trait implemented by some generic argument <I as Iterator>::effect: const could be different


#![allow(unused)]
fn main() {
I: Iterator<effect = ?async + ?const>

// or

// Associated type bounds [RFC 2289](https://github.com/rust-lang/rfcs/blob/master/text/2289-associated-type-bounds.md)
I: Iterator<effect: ?async + ?const>
}

The current way mimics how associated types are bound


#![allow(unused)]
fn main() {
fn print_iter<I, effect>(iter: I)
where
    effect: ?async,
    I: Iterator,
    <I as Iterator>::Item: Display,
    <I as Iterator>::effect: ?async;
}

Explicit generic over all modifier keywords

The syntax does not give shortans for specifying all modifiers at once. Instead, the function, trait or type should explicit bound over all keywords it could be generic.

Although being inconvenient to list it manually, this has some advantages over the generic over all keywords available syntax.

Explicit

Readers does not have to remind which keywords are available that may need to be implemented in some specific way.

Backwards compatible to introduce new keywords in the language

Allowing the generic over all means that in case a new keyword lands, all complete generic functions and traits may be affected by the keyword, requiring at least some considerations on the side effects.

Limitations

These are some limitations (hopefully, not yet supported features) noticed in the syntax.

Effect sets

Function generic over sets of effects, limiting it to be called by only one group of effects.


#![allow(unused)]
fn main() {
fn compute<effect<KernelSpace | UserSpace | PreComputed>>() -> Response
where
    effect<KernelSpace>: !alloc + !panic + !async,
    effect<UserSpace>: alloc + ?async,
    effect<PreComputed>: const
{
    if effect<KernelSpace> {
        // ensures that in "KernelSpace" will not alloc, panic or run futures
    }
    if effect<UserSpace> {
        // allow allocations and futures
    }
    if effect<PreComputed> {
        // only compile-time evaluation
    }
}

fn caller1()
where
    effect: ?alloc + !panic + !async
{
    compute<effect<KernelSpace>>(); // allowed
}

fn caller2()
where
    effect: alloc + async
{
    compute<effect<KernelSpace>>(); // allowed
    compute<effect<UserSpace>>(); // allowed
}

fn caller3()
where
    effect: !alloc
{
    compute<effect<UserSpace>>(); // compiler error
}

fn caller4()
where
    effect: const
{
    compute<effect<PreComputed>>(); // allowed
}
}