• Name: effect-as-a-clause
  • Proposed by: @mominul @satvikpendem
  • Original proposal (optional): (https://github.com/rust-lang/keyword-generics-initiative/issues/14)

Design

We want to propose the usage of the effect clause to achieve operation genericity, for example:

#![allow(unused)] fn main() { trait Read { fn read(&mut self, buf: &mut [u8]) -> Result<usize> effect async; fn read_to_string(&mut self, buf: &mut String) -> Result<usize> effect async { .. } } /// Function to read from the file into a string which may exhibit async or const effect fn read_to_string(path: &str) -> io::Result<String> effect async, const { let mut string = String::new(); // We can be conditional over the context the function has been called from, // only when the function declaration has the `effect` clause if async || !async { let mut file = File::open("foo.txt")?; // File implements Read // Because `read_to_string` is also an `effect` function that may or may not exhibit // async-ness par the declaration, we can use it on both contexts (async/sync) // we are placing the condition on. file.read_to_string(&mut string)?; // .await will be inferred. } else { // must be const // As the `read_to_string` doesn't exhibit const-ness, we'll need to handle it ourselves. string = include_str!(path).to_string(); } Ok(string) } /// A normal function fn read() { let data = read_to_string("hello.txt").unwrap(); } /// A async function async fn read() { let data = read_to_string("hello.txt").await.unwrap(); } /// A const function const fn read() { let data = read_to_string("hello.txt").unwrap(); } }

So in a nutshell, a function declaration with an effect clause is a special type of function that may or may not exhibit async or const behavior(effect) and it depends on the context of the function being called from and we can execute a different piece of code according to the context from the function was called from too (like the const_eval_select, resolves #6):

#![allow(unused)] fn main() { fn function() -> Result<()> effect async, const { // ... if async { // code for handling stuff asynchronously } else if const { // code for handling stuff `const`-way else { // code for handling stuff synchronously } // ... } }

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 { type Item; async fn next(&mut self) -> Option<Self::Item>; fn size_hint(&self) -> (usize, Option<usize>); } pub async fn find<I, T, P>(iter: &mut I, predicate: P) -> Option<T> where I: Iterator<Item = T> + Sized, P: async FnMut(&T) -> bool; }

maybe async

#![allow(unused)] fn main() { pub trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item> effect async; fn size_hint(&self) -> (usize, Option<usize>); } pub fn find<I, T, P>(iter: &mut I, predicate: P) -> Option<T> where I: Iterator<Item = T> + Sized, P: FnMut(&T) -> bool effect async; effect async }

generic over all modifier keywords

#![allow(unused)] fn main() { pub trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item> effect async, const; fn size_hint(&self) -> (usize, Option<usize>); } pub fn find<I, T, P>(iter: &mut I, predicate: P) -> Option<T> where I: Iterator<Item = T> + Sized, P: FnMut(&T) -> bool effect async, const; effect async, const }

Notes

We can introduce maybe keyword instead of effect if it seems more appropriate terminology for the semantics described in this proposal.