Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/source/books/hyperactor-book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,6 @@
- [`#[derive(Named)]`](macros/named.md)
- [`#[export]`](macros/export.md)
- [`#[forward]`](macros/forward.md)
- [`#[alias]`](macros/alias.md)
- [`#[behavior]`](macros/behavior.md)
- [Appendix](appendix/index.md)
- [Typed Message Lifecycle](appendix/lifecycle.md)
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# `#[alias]`
# `#[behavior]`

The `#[alias]` macro defines a façade actor type that exposes only a selected set of messages.
This allows you to hand out **stable or restricted APIs** without tying clients to the full concrete actor type.
The `#[behavior]` macro defines a type which represents an actor that handles a set of messages.
Behaviors allows you to define and hand out **stable or restricted APIs** without tying clients
to the full concrete actor type.

### Defining an alias
### Defining a behavior

An alias groups together one or more message enums or structs:
A behavior groups together one or more message enums or structs:

```rust
#[derive(Handler)]
Expand All @@ -28,23 +29,23 @@ struct GetItemCount<C> {
reply: OncePortRef<C>,
}

// Define an alias actor `ShoppingApi` that exposes exactly these messages.
hyperactor::alias!(
// Define a behavior `ShoppingApi` that exposes exactly these messages.
hyperactor::behavior!(
ShoppingApi,
ShoppingList,
ClearList,
GetItemCount<usize>,
);
```

The alias can include:
The behavior can include any type of message:
- Enums (e.g. `ShoppingList`)
- Struct messages (e.g. `ClearList`, `GetItemCount<usize>`)
- Generic messages, with concrete type parameters bound at the alias site.

### Using an alias
### Using a behavior

After spawning the real actor, re-type its id as the alias:
After spawning the real actor, re-type its id as the behavior:

```rust
let mut proc = Proc::local();
Expand All @@ -69,14 +70,14 @@ println!("items containing 'dairy': {n}");
shopping_api.clear_list(&client, "end of session".into()).await?;
```

> **Note:** `alias!` does *not* rename methods. It authorizes those calls on
> **Note:** `behavior!` does *not* rename methods. It authorizes those calls on
> `ActorRef<ShoppingApi>` if and only if the message type was included.
> **Note:** `attest` is a low-level escape hatch. It asserts that a raw
> `ActorId` is valid for the alias type. This example uses it only because
> `ActorId` is valid for the behavior. This example uses it only because
> we just spawned the actor and know the id is safe.
> In general, prefer higher-level APIs (such as `Proc` utilities) for
> constructing alias references, and use `attest` sparingly.
> constructing behavior references, and use `attest` sparingly.
### Generated code (excerpt)

Expand Down Expand Up @@ -108,12 +109,12 @@ impl hyperactor::actor::RemoteHandles<GetItemCount<usize>> for ShoppingApi {}

### Capability slicing

If a message type is not listed in the alias, trying to call it will fail at compile time:
If a message type is not listed in the behavior, trying to call it will fail at compile time:

```rust
// If ClearList were omitted from the alias:
// If ClearList were omitted from the behavior:
shopping_api.clear_list(&client, "...").await?;
// ^ error: the trait bound `ShoppingApi: RemoteHandles<ClearList>` is not satisfied
```

This makes `alias!` a useful tool for **compile-time capability control**.
This makes `behavior!` a useful tool for **compile-time capability control**.
4 changes: 2 additions & 2 deletions hyperactor/example/derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,10 @@ impl GetItemCountHandler<usize> for ShoppingListActor {
}
}

// Define an alias actor `ShoppingApi`. Clients can use
// Define a behavior `ShoppingApi`. Clients can use
// `ActorRef<ShoppingApi>` instead of referencing the concrete
// `ShoppingListActor` directly.
hyperactor::alias!(ShoppingApi, ShoppingList, ClearList, GetItemCount<usize>,);
hyperactor::behavior!(ShoppingApi, ShoppingList, ClearList, GetItemCount<usize>,);

#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
Expand Down
10 changes: 5 additions & 5 deletions hyperactor/src/actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -689,7 +689,7 @@ impl<A: Actor> Clone for ActorHandle<A> {
/// remote references across process boundaries.
///
/// It is not limited to concrete [`Actor`] implementations. For
/// example, façade types generated by [`alias!`] implement
/// example, façade types generated by [`behavior!`] implement
/// `Referable` so that you can hand out restricted or stable APIs
/// while still using the same remote messaging machinery.
///
Expand All @@ -700,7 +700,7 @@ impl<A: Actor> Clone for ActorHandle<A> {
/// boundaries.
///
/// In contrast, [`RemotableActor`] is the trait that marks *actors*
/// that can actually be **spawned remotely**. An alias type may be a
/// that can actually be **spawned remotely**. A behavior may be a
/// `Referable` but is never a `RemotableActor`.
pub trait Referable: Named + Send + Sync {}

Expand Down Expand Up @@ -1033,15 +1033,15 @@ mod tests {
}

#[tokio::test]
async fn test_ref_alias() {
async fn test_ref_behavior() {
let test = MultiValuesTest::new().await;

test.send(123u64);
test.send("foo".to_string());

hyperactor::alias!(MyActorAlias, u64, String);
hyperactor::behavior!(MyActorBehavior, u64, String);

let myref: ActorRef<MyActorAlias> = test.handle.bind();
let myref: ActorRef<MyActorBehavior> = test.handle.bind();
myref.port().send(&test.client, "biz".to_string()).unwrap();
myref.port().send(&test.client, 999u64).unwrap();

Expand Down
2 changes: 1 addition & 1 deletion hyperactor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ pub use hyperactor_macros::RefClient;
#[doc(inline)]
pub use hyperactor_macros::Unbind;
#[doc(inline)]
pub use hyperactor_macros::alias;
pub use hyperactor_macros::behavior;
#[doc(inline)]
pub use hyperactor_macros::export;
#[doc(inline)]
Expand Down
39 changes: 21 additions & 18 deletions hyperactor_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1603,49 +1603,52 @@ pub fn export(attr: TokenStream, item: TokenStream) -> TokenStream {
TokenStream::from(expanded)
}

/// Represents the full input to [`fn alias`].
struct AliasInput {
alias: Ident,
/// Represents the full input to [`fn behavior`].
struct BehaviorInput {
behavior: Ident,
handlers: Vec<HandlerSpec>,
}

impl syn::parse::Parse for AliasInput {
impl syn::parse::Parse for BehaviorInput {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let alias: Ident = input.parse()?;
let behavior: Ident = input.parse()?;
let _: Token![,] = input.parse()?;
let raw_handlers = input.parse_terminated(HandlerSpec::parse, Token![,])?;
let handlers = raw_handlers.into_iter().collect();
Ok(AliasInput { alias, handlers })
Ok(BehaviorInput { behavior, handlers })
}
}

/// Create a [`Referable`] handling a specific set of message types.
/// This is used to create an [`ActorRef`] without having to depend on the
/// Create a [`Referable`] definition, handling a specific set of message types.
/// Behaviors are used to create an [`ActorRef`] without having to depend on the
/// actor's implementation. If the message type need to be cast, add `castable`
/// flag to those types. e.g. the following example creats an alias with 5
/// flag to those types. e.g. the following example creates a behavior with 5
/// message types, and 4 of which need to be cast.
///
/// ```
/// hyperactor::alias!(
/// TestActorAlias,
/// hyperactor::behavior!(
/// TestActorBehavior,
/// TestMessage { castable = true },
/// () {castable = true },
/// MyGeneric<()> {castable = true },
/// u64,
/// );
/// ```
#[proc_macro]
pub fn alias(input: TokenStream) -> TokenStream {
let AliasInput { alias, handlers } = parse_macro_input!(input as AliasInput);
pub fn behavior(input: TokenStream) -> TokenStream {
let BehaviorInput {
behavior: behavior,
handlers,
} = parse_macro_input!(input as BehaviorInput);
let tys = HandlerSpec::add_indexed(handlers);

let expanded = quote! {
#[doc = "The generated alias struct."]
#[doc = "The generated behavior struct."]
#[derive(Debug, hyperactor::Named, serde::Serialize, serde::Deserialize)]
pub struct #alias;
impl hyperactor::actor::Referable for #alias {}
pub struct #behavior;
impl hyperactor::actor::Referable for #behavior {}

impl<A> hyperactor::actor::Binds<A> for #alias
impl<A> hyperactor::actor::Binds<A> for #behavior
where
A: hyperactor::Actor #(+ hyperactor::Handler<#tys>)* {
fn bind(ports: &hyperactor::proc::Ports<A>) {
Expand All @@ -1656,7 +1659,7 @@ pub fn alias(input: TokenStream) -> TokenStream {
}

#(
impl hyperactor::actor::RemoteHandles<#tys> for #alias {}
impl hyperactor::actor::RemoteHandles<#tys> for #behavior {}
)*
};

Expand Down
2 changes: 1 addition & 1 deletion hyperactor_macros/tests/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ impl Handler<u64> for TestActor {
}
}

hyperactor::alias!(
hyperactor::behavior!(
TestActorAlias,
TestMessage { cast = true },
() { cast = true },
Expand Down
6 changes: 3 additions & 3 deletions hyperactor_multiprocess/src/supervision.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,6 @@ impl ProcSupervisionState {
}
}

hyperactor::alias!(ProcSupervisor, ProcSupervisionMessage); // For proc supervisor to implement (e.g. system actor)
hyperactor::alias!(WorldSupervisor, WorldSupervisionMessage); // For world supervisor to implement (e.g. system actor)
hyperactor::alias!(SupervisionClient, WorldSupervisionState); // For the end receiver of supervision events to implement (e.g. client)
hyperactor::behavior!(ProcSupervisor, ProcSupervisionMessage); // For proc supervisor to implement (e.g. system actor)
hyperactor::behavior!(WorldSupervisor, WorldSupervisionMessage); // For world supervisor to implement (e.g. system actor)
hyperactor::behavior!(SupervisionClient, WorldSupervisionState); // For the end receiver of supervision events to implement (e.g. client)
2 changes: 1 addition & 1 deletion monarch_messages/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,4 @@ pub enum ClientMessage {
},
}

hyperactor::alias!(ClientActor, ClientMessage);
hyperactor::behavior!(ClientActor, ClientMessage);
2 changes: 1 addition & 1 deletion monarch_messages/src/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,4 +237,4 @@ pub enum ControllerMessage {
},
}

hyperactor::alias!(ControllerActor, ControllerMessage);
hyperactor::behavior!(ControllerActor, ControllerMessage);
2 changes: 1 addition & 1 deletion monarch_messages/src/debugger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ pub enum DebuggerMessage {
Action { action: DebuggerAction },
}

hyperactor::alias!(
hyperactor::behavior!(
DebuggerActor,
DebuggerMessage { cast = true },
);
2 changes: 1 addition & 1 deletion monarch_messages/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -941,7 +941,7 @@ pub struct WorkerParams {
pub controller_actor: ActorRef<ControllerActor>,
}

hyperactor::alias!(
hyperactor::behavior!(
WorkerActor,
WorkerMessage { cast = true },
);