2

Add `Context::ext` by jkarneges · Pull Request #123203 · rust-lang/rust · GitHub

 4 weeks ago
source link: https://github.com/rust-lang/rust/pull/123203
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Conversation

Contributor

This change enables Context to carry arbitrary extension data via a single &mut dyn Any field.

#![feature(context_ext)]

impl Context {
    fn ext(&mut self) -> &mut dyn Any;
}

impl ContextBuilder {
    fn ext(self, data: &'a mut dyn Any) -> Self;

    fn from(cx: &'a mut Context<'_>) -> Self;
    fn waker(self, waker: &'a Waker) -> Self;
}

Basic usage:

struct MyExtensionData {
    executor_name: String,
}

let mut ext = MyExtensionData {
    executor_name: "foo".to_string(),
};

let mut cx = ContextBuilder::from_waker(&waker).ext(&mut ext).build();

if let Some(ext) = cx.ext().downcast_mut::<MyExtensionData>() {
    println!("{}", ext.executor_name);
}

Currently, Context only carries a Waker, but there is interest in having it carry other kinds of data. Examples include LocalWaker, a reactor interface, and multiple arbitrary values by type. There is also a general practice in the ecosystem of sharing data between executors and futures via thread-locals or globals that would arguably be better shared via Context, if it were possible.

The ext field would provide a low friction (to stabilization) solution to enable experimentation. It would enable experimenting with what kinds of data we want to carry as well as with what data structures we may want to use to carry such data.

Dedicated fields for specific kinds of data could still be added directly on Context when we have sufficient experience or understanding about the problem they are solving, such as with LocalWaker. The ext field would be for data for which we don't have such experience or understanding, and that could be graduated to dedicated fields once proven.

Both the provider and consumer of the extension data must be aware of the concrete type behind the Any. This means it is not possible for the field to carry an abstract interface. However, the field can carry a concrete type which in turn carries an interface. There are different ways one can imagine an interface-carrying concrete type to work, hence the benefit of being able to experiment with such data structures.

Passing interfaces

Interfaces can be placed in a concrete type, such as a struct, and then that type can be casted to Any. However, one gotcha is Any cannot contain non-static references. This means one cannot simply do:

struct Extensions<'a> {
    interface1: &'a mut dyn Trait1,
    interface2: &'a mut dyn Trait2,
}

let mut ext = Extensions {
    interface1: &mut impl1,
    interface2: &mut impl2,
};

let ext: &mut dyn Any = &mut ext;

To work around this without boxing, unsafe code can be used to create a safe projection using accessors. For example:

pub struct Extensions {
    interface1: *mut dyn Trait1,
    interface2: *mut dyn Trait2,
}

impl Extensions {
    pub fn new<'a>(
        interface1: &'a mut (dyn Trait1 + 'static),
        interface2: &'a mut (dyn Trait2 + 'static),
        scratch: &'a mut MaybeUninit<Self>,
    ) -> &'a mut Self {
        scratch.write(Self {
            interface1,
            interface2,
        })
    }

    pub fn interface1(&mut self) -> &mut dyn Trait1 {
        unsafe { self.interface1.as_mut().unwrap() }
    }

    pub fn interface2(&mut self) -> &mut dyn Trait2 {
        unsafe { self.interface2.as_mut().unwrap() }
    }
}

let mut scratch = MaybeUninit::uninit();
let ext: &mut Extensions = Extensions::new(&mut impl1, &mut impl2, &mut scratch);

// ext can now be casted to `&mut dyn Any` and back, and used safely
let ext: &mut dyn Any = ext;

Context inheritance

Sometimes when futures poll other futures they want to provide their own Waker which requires creating their own Context. Unfortunately, polling sub-futures with a fresh Context means any properties on the original Context won't get propagated along to the sub-futures. To help with this, some additional methods are added to ContextBuilder.

Here's how to derive a new Context from another, overriding only the Waker:

let mut cx = ContextBuilder::from(parent_cx).waker(&new_waker).build();

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK