Although I have minimized it as much as possible, my problem is quite intricate. Playground at the bottom. I am going to tip you if you can solve the issue, because I am stuck.
Library
I am implementing a library for actors. It contains an ActorBounds
trait with a spawn
method.
This method takes a closure as argument, which it saves and returns in an ActorTask
struct.
This struct has a run
method that calls the inner closure.
These traits and structs have many types, whose purpose will subsequently become clear by means of an example.
pub trait ActorBounds<M> {
type ChildActorBoundsType<M2>;
type ChildActorBounds<M2>: ActorBounds<M2>;
fn spawn<M2, F>(
&self,
f: F,
) -> ActorTask<M2, F, Self::ChildActorBoundsType<M2>, Self::ChildActorBounds<M2>>
where
F: FnOnce(Self::ChildActorBounds<M2>);
}
pub struct ActorTask<M, F, CABT, CAB> {
m: PhantomData<M>,
f: F,
cell: ActorCell<M, CABT>,
t: PhantomData<CAB>,
}
impl<M, F, CABT, CAB> ActorTask<M, F, CABT, CAB>
where
F: FnOnce(ActorCell<M, CABT>),
{
fn run(self) {
let f = self.f;
let cell = self.cell;
&cell.m; // must be able to access m field
f(cell);
}
}
The ActorBound
trait is implemented by an ActorCell
struct:
pub struct ActorCell<M, AB> {
pub m: PhantomData<M>,
pub t: AB,
}
There are the two distinct implementations:
Implementation 1
struct StandardBounds<M>(PhantomData<M>);
impl<M> ActorBounds<M> for ActorCell<M, StandardBounds<M>> {
type ChildActorBoundsType<M2> = StandardBounds<M2>;
type ChildActorBounds<M2> = ActorCell<M2, StandardBounds<M2>>;
fn spawn<M2, F>(
&self,
f: F,
) -> ActorTask<M2, F, StandardBounds<M2>, ActorCell<M2, StandardBounds<M2>>>
where
F: FnOnce(ActorCell<M2, StandardBounds<M2>>),
{
let cell = ActorCell {
m: PhantomData::<M2>,
t: StandardBounds(PhantomData::<M2>),
};
ActorTask {
m: PhantomData::<M2>,
f,
cell,
t: PhantomData::<ActorCell<M2, StandardBounds<M2>>>,
}
}
}
Implementation 2
struct TestBounds<M>(PhantomData<M>);
impl<M> ActorBounds<M> for ActorCell<M, TestBounds<M>> {
type ChildActorBoundsType<M2> = TestBounds<M2>;
type ChildActorBounds<M2> = ActorCell<M2, TestBounds<M2>>;
fn spawn<M2, F>(&self, f: F) -> ActorTask<M2, F, TestBounds<M2>, ActorCell<M2, TestBounds<M2>>>
where
F: FnOnce(ActorCell<M2, TestBounds<M2>>),
{
let cell = ActorCell {
m: PhantomData::<M2>,
t: TestBounds(PhantomData::<M2>),
};
ActorTask {
m: PhantomData::<M2>,
f,
cell,
t: PhantomData::<ActorCell<M2, TestBounds<M2>>>,
}
}
}
Example usage
The user of the library will use the ActorBounds
trait as follows:
async fn takes_cell<AB>(mut cellu64: AB)
where
AB: ActorBounds<u64>,
{
let task = cellu64.spawn::<u32, _>(|cellu32| {
let task = cellu32.spawn::<u16, _>(|cellu16| {
let task = cellu16.spawn::<u8, _>(|cellu8| {
//
});
task.run();
});
task.run();
});
task.run();
}
The user would call the function as follows. Notice how the two ActorCell
variables have different types but they both work as they respect the prescribed bound:
fn main() {
let cell = ActorCell {
m: PhantomData::<u64>,
t: StandardBounds(PhantomData::<u64>),
};
takes_cell(cell); // ActorCell<u64, StandardBounds<u64>>
let cell = ActorCell {
m: PhantomData::<u64>,
t: TestBounds(PhantomData::<u64>),
};
takes_cell(cell); // ActorCell<u64, TestBounds<u64>>
}
Logical purpose of the types
Consider the first takes_cell
call, where the user passes an ActorCell<u64, StandardBounds<u64>
in.
Logically, the intended effect is as follows:
async fn takes_cell<AB>(mut cellu64: AB) // cellu64 = ActorCell<u64, StandardBounds<u64>
where
AB: ActorBounds<u64>,
{
let task = cellu64.spawn::<u32, _>(|cellu32| { // cellu32 = ActorCell<u32, StandardBounds<u32>
let task = cellu32.spawn::<u16, _>(|cellu16| { // cellu16 = ActorCell<u16, StandardBounds<u16>
let task = cellu16.spawn::<u8, _>(|cellu8| { // cellu8 = ActorCell<u8, StandardBounds<u8>
//
});
task.run();
});
task.run();
});
task.run();
}
The initial cell has StandardBounds
. All the cells spawned from it have StandardBounds
too.
Error
Consider a simplified version of takes_cell
where I spawn and run one cell only. It fails to compile due to unsatisfying trait bounds in run
:
Simplified takes_cell
async fn takes_cell<AB>(mut cellu64: AB)
where
AB: ActorBounds<u64>,
{
let task = cellu64.spawn::<u32, _>(|cellu32| {
//
});
task.run();
}
Compiler error
error[E0599]: the method `run` exists for struct `ActorTask<u32, {closure@main.rs:93:40}, <AB as ActorBounds<u64>>::ChildActorBoundsType<u32>, ...>`, but its trait bounds were not satisfied
--> src/main.rs:102:10
|
21 | pub struct ActorTask<M, F, CABT, CAB> {
| ------------------------------------- method `run` not found for this struct
...
93 | let task = cellu64.spawn::<u32, _>(|cellu32| {
| --------- doesn't satisfy `<_ as FnOnce<(ActorCell<u32, <AB as ActorBounds<u64>>::ChildActorBoundsType<u32>>,)>>::Output = ()` or `_: FnOnce<(ActorCell<u32, <AB as ActorBounds<u64>>::ChildActorBoundsType<u32>>,)>`
...
102 | task.run();
| ^^^ method cannot be called due to unsatisfied trait bounds
|
= note: the full type name has been written to '/home/steddy/RustroverProjects/reproducers/target/debug/deps/reproducers-3be11c635dc30962.long-type-9967442127667851649.txt'
note: the following trait bounds were not satisfied:
`<{closure@src/main.rs:93:40: 93:49} as FnOnce<(ActorCell<u32, <AB as ActorBounds<u64>>::ChildActorBoundsType<u32>>,)>>::Output = ()`
`{closure@src/main.rs:93:40: 93:49}: FnOnce<(ActorCell<u32, <AB as ActorBounds<u64>>::ChildActorBoundsType<u32>>,)>`
--> src/main.rs:30:8
|
28 | impl<M, F, CABT, CAB> ActorTask<M, F, CABT, CAB>
| --------------------------
29 | where
30 | F: FnOnce(ActorCell<M, CABT>),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound introduced here
Requirements
How can I make the code compile respecting the following requirement?
The
ActorTask::run
method must be able to access the m
field in ActorCell
.
Ideally, takes_cell
should remain unaltered, in the sense that the user does not have to type too many complicated additional trait bounds over AB
.
My intuition: Is this perhaps achievable by modifying the bounds on the associated types in ActorBounds
of the bounds of ActorBounds::spawn
?