diff --git a/docs/modules/rust/index.md b/docs/modules/rust/index.md index 443f817..83a751b 100644 --- a/docs/modules/rust/index.md +++ b/docs/modules/rust/index.md @@ -23,7 +23,7 @@ struct Location { Let's start with a highly commented example, straight from the [demo]. This Rust package defines a SpacetimeDB module, with types we can operate on and functions we can run. ```rust -// In this small example, we have two rust imports: +// In this small example, we have two Rust imports: // |spacetimedb::spacetimedb| is the most important attribute we'll be using. // |spacetimedb::println| is like regular old |println|, but outputting to the module's logs. use spacetimedb::{spacetimedb, println}; @@ -31,7 +31,7 @@ use spacetimedb::{spacetimedb, println}; // This macro lets us interact with a SpacetimeDB table of Person rows. // We can insert and delete into, and query, this table by the collection // of functions generated by the macro. -#[spacetimedb(table(public))] +#[table(name = person, public)] pub struct Person { name: String, } @@ -39,26 +39,26 @@ pub struct Person { // This is the other key macro we will be using. A reducer is a // stored procedure that lives in the database, and which can // be invoked remotely. -#[spacetimedb(reducer)] -pub fn add(name: String) { +#[reducer] +pub fn add(ctx: &ReducerContext, name: String) { // |Person| is a totally ordinary Rust struct. We can construct // one from the given name as we typically would. let person = Person { name }; // Here's our first generated function! Given a |Person| object, // we can insert it into the table: - Person::insert(person) + ctx.db.person().insert(person); } // Here's another reducer. Notice that this one doesn't take any arguments, while // |add| did take one. Reducers can take any number of arguments, as long as -// SpacetimeDB knows about all their types. Reducers also have to be top level +// SpacetimeDB recognizes their types. Reducers also have to be top level // functions, not methods. -#[spacetimedb(reducer)] -pub fn say_hello() { +#[reducer] +pub fn say_hello(ctx: &ReducerContext) { // Here's the next of our generated functions: |iter()|. This // iterates over all the columns in the |Person| table in SpacetimeDB. - for person in Person::iter() { + for person in ctx.db.person().iter() { // Reducers run in a very constrained and sandboxed environment, // and in particular, can't do most I/O from the Rust standard library. // We provide an alternative |spacetimedb::println| which is just like @@ -72,13 +72,13 @@ pub fn say_hello() { // the reducer must have a return type of `Result<(), T>`, for any `T` that // implements `Debug`. Such errors returned from reducers will be formatted and // printed out to logs. -#[spacetimedb(reducer)] -pub fn add_person(name: String) -> Result<(), String> { +#[reducer] +pub fn add_person(ctx: &ReducerContext, name: String) -> Result<(), String> { if name.is_empty() { return Err("Name cannot be empty"); } - Person::insert(Person { name }) + ctx.db.person().insert(Person { name }) } ``` @@ -88,15 +88,15 @@ Now we'll get into details on all the macro APIs SpacetimeDB provides, starting ### Defining tables -The `#[spacetimedb(table)]` is applied to a Rust struct with named fields. +The `#[table(name = table_name)]` macro is applied to a Rust struct with named fields. By default, tables are considered **private**. This means that they are only readable by the table owner, and by server module code. -The `#[spacetimedb(table(public))]` macro makes a table public. **Public** tables are readable by all users, but can still only be modified by your server module code. +The `#[table(name = table_name, public)]` macro makes a table public. **Public** tables are readable by all users, but can still only be modified by your server module code. _Coming soon: We plan to add much more robust access controls than just public or private. Stay tuned!_ ```rust -#[spacetimedb(table(public))] -struct Table { +#[table(name = my_table, public)] +struct MyTable { field1: String, field2: u32, } @@ -104,7 +104,7 @@ struct Table { This attribute is applied to Rust structs in order to create corresponding tables in SpacetimeDB. Fields of the Rust struct correspond to columns of the database table. -The fields of the struct have to be types that spacetimedb knows how to encode into the database. This is captured in Rust by the `SpacetimeType` trait. +The fields of the struct have to be types that SpacetimeDB knows how to encode into the database. This is captured in Rust by the `SpacetimeType` trait. This is automatically defined for built in numeric types: @@ -120,10 +120,10 @@ And common data structures: - `Option where T: SpacetimeType` - `Vec where T: SpacetimeType` -All `#[spacetimedb(table)]` types are `SpacetimeType`s, and accordingly, all of their fields have to be. +All `#[table(..)]` types are `SpacetimeType`s, and accordingly, all of their fields have to be. ```rust -#[spacetimedb(table(public))] +#[table(name = another_table, public)] struct AnotherTable { // Fine, some builtin types. id: u64, @@ -155,7 +155,7 @@ enum Serial { Once the table is created via the macro, other attributes described below can control more aspects of the table. For instance, a particular column can be indexed, or take on values of an automatically incremented counter. These are described in detail below. ```rust -#[spacetimedb(table(public))] +#[table(name = person, public)] struct Person { #[unique] id: u64, @@ -167,30 +167,30 @@ struct Person { ### Defining reducers -`#[spacetimedb(reducer)]` is always applied to top level Rust functions. They can take arguments of types known to SpacetimeDB (just like fields of structs must be known to SpacetimeDB), and either return nothing, or return a `Result<(), E: Debug>`. +`#[reducer]` is always applied to top level Rust functions. They can take arguments of types known to SpacetimeDB (just like fields of structs must be known to SpacetimeDB), and either return nothing, or return a `Result<(), E: Debug>`. ```rust -#[spacetimedb(reducer)] -fn give_player_item(player_id: u64, item_id: u64) -> Result<(), GameErr> { +#[reducer] +fn give_player_item(ctx: &ReducerContext, player_id: u64, item_id: u64) -> Result<(), GameErr> { // Notice how the exact name of the filter function derives from // the name of the field of the struct. - let mut item = Item::find_by_item_id(id).ok_or(GameErr::InvalidId)?; + let mut item = ctx.db.item().item_id().find(id).ok_or(GameErr::InvalidId)?; item.owner = Some(player_id); - Item::update_by_id(id, item); + ctx.db.item().item_id().update(item); Ok(()) } +#[table(name = item, public)] struct Item { - #[unique] + #[primary_key] item_id: u64, - owner: Option, } ``` Note that reducers can call non-reducer functions, including standard library functions. -There are several macros which modify the semantics of a column, which are applied to the members of the table struct. `#[unique]` and `#[autoinc]` are covered below, describing how those attributes affect the semantics of inserting, filtering, and so on. +There are several macros which modify the semantics of a column, which are applied to the members of the table struct. `#[primary_key]`, `#[unique]` and `#[autoinc]` are covered below, describing how those attributes affect the semantics of inserting, filtering, and so on. #[SpacetimeType] @@ -202,7 +202,7 @@ Tables can be used to schedule a reducer calls either at a specific timestamp or ```rust // The `scheduled` attribute links this table to a reducer. -#[spacetimedb(table, scheduled(send_message))] +#[table(name = send_message_timer, scheduled(send_message)] struct SendMessageTimer { text: String, } @@ -211,10 +211,10 @@ struct SendMessageTimer { The `scheduled` attribute adds a couple of default fields and expands as follows: ```rust -#[spacetimedb(table)] +#[table(name = send_message_timer, scheduled(send_message)] struct SendMessageTimer { text: String, // original field - #[primary] + #[primary_key] #[autoinc] scheduled_id: u64, // identifier for internal purpose scheduled_at: ScheduleAt, //schedule details @@ -230,21 +230,21 @@ pub enum ScheduleAt { } ``` -Managing timers with scheduled table is as simple as inserting or deleting rows from table. +Managing timers with a scheduled table is as simple as inserting or deleting rows from the table. ```rust -#[spacetimedb(reducer)] - -// Reducers linked to the scheduler table should have their first argument as `ReducerContext` +#[reducer] +// Reducers linked to the scheduler table should have their first argument as `&ReducerContext` // and the second as an instance of the table struct it is linked to. -fn send_message(ctx: ReducerContext, arg: SendMessageTimer) -> Result<(), String> { +fn send_message(ctx: &ReducerContext, arg: SendMessageTimer) -> Result<(), String> { // ... } // Scheduling reducers inside `init` reducer -fn init() { +#[reducer(init)] +fn init(ctx: &ReducerContext) { // Scheduling a reducer for a specific Timestamp - SendMessageTimer::insert(SendMessageTimer { + ctx.db.send_message_timer().insert(SendMessageTimer { scheduled_id: 1, text:"bot sending a message".to_string(), //`spacetimedb::Timestamp` implements `From` trait to `ScheduleAt::Time`. @@ -252,7 +252,7 @@ fn init() { }); // Scheduling a reducer to be called at fixed interval of 100 milliseconds. - SendMessageTimer::insert(SendMessageTimer { + ctx.db.send_message_timer().insert(SendMessageTimer { scheduled_id: 0, text:"bot sending a message".to_string(), //`std::time::Duration` implements `From` trait to `ScheduleAt::Duration`. @@ -282,8 +282,8 @@ use spacetimedb::{ dbg, }; -#[spacetimedb(reducer)] -fn output(i: i32) { +#[reducer] +fn output(ctx: &ReducerContext, i: i32) { // These will be logged at log::Level::Info. println!("an int with a trailing newline: {i}"); print!("some more text...\n"); @@ -297,7 +297,7 @@ fn output(i: i32) { // before passing the value of |i| along to the calling function. // // The output is logged log::Level::Debug. - OutputtedNumbers::insert(dbg!(i)); + ctx.db.outputted_number().insert(dbg!(i)); } ``` @@ -308,7 +308,7 @@ We'll work off these structs to see what functions SpacetimeDB generates: This table has a plain old column. ```rust -#[spacetimedb(table(public))] +#[table(name = ordinary, public)] struct Ordinary { ordinary_field: u64, } @@ -317,7 +317,7 @@ struct Ordinary { This table has a unique column. Every row in the `Unique` table must have distinct values of the `unique_field` column. Attempting to insert a row with a duplicate value will fail. ```rust -#[spacetimedb(table(public))] +#[table(name = unique, public)] struct Unique { // A unique column: #[unique] @@ -330,7 +330,7 @@ This table has an automatically incrementing column. SpacetimeDB automatically p Only integer types can be `#[unique]`: `u8`, `u16`, `u32`, `u64`, `u128`, `i8`, `i16`, `i32`, `i64` and `i128`. ```rust -#[spacetimedb(table(public))] +#[table(name = autoinc, public)] struct Autoinc { #[autoinc] autoinc_field: u64, @@ -340,7 +340,7 @@ struct Autoinc { These attributes can be combined, to create an automatically assigned ID usable for filtering. ```rust -#[spacetimedb(table(public))] +#[table(name = identity, public)] struct Identity { #[autoinc] #[unique] @@ -352,15 +352,15 @@ struct Identity { We'll talk about insertion first, as there a couple of special semantics to know about. -When we define |Ordinary| as a spacetimedb table, we get the ability to insert into it with the generated `Ordinary::insert` method. +When we define |Ordinary| as a SpacetimeDB table, we get the ability to insert into it with the generated `ctx.db.ordinary().insert(..)` method. Inserting takes a single argument, the row to insert. When there are no unique fields in the row, the return value is the inserted row. ```rust -#[spacetimedb(reducer)] -fn insert_ordinary(value: u64) { +#[reducer] +fn insert_ordinary(ctx: &ReducerContext, value: u64) { let ordinary = Ordinary { ordinary_field: value }; - let result = Ordinary::insert(ordinary); + let result = ctx.db.ordinary().insert(ordinary); assert_eq!(ordinary.ordinary_field, result.ordinary_field); } ``` @@ -370,12 +370,12 @@ When there is a unique column constraint on the table, insertion can fail if a u If we insert two rows which have the same value of a unique column, the second will fail. ```rust -#[spacetimedb(reducer)] -fn insert_unique(value: u64) { - let result = Unique::insert(Unique { unique_field: value }); +#[reducer] +fn insert_unique(ctx: &ReducerContext, value: u64) { + let result = ctx.db.unique().insert(Unique { unique_field: value }); assert!(result.is_ok()); - let result = Unique::insert(Unique { unique_field: value }); + let result = ctx.db.unique().insert(Unique { unique_field: value }); assert!(result.is_err()); } ``` @@ -385,26 +385,26 @@ When inserting a table with an `#[autoinc]` column, the database will automatica The returned row has the `autoinc` column set to the value that was actually written into the database. ```rust -#[spacetimedb(reducer)] -fn insert_autoinc() { +#[reducer] +fn insert_autoinc(ctx: &ReducerContext) { for i in 1..=10 { // These will have values of 1, 2, ..., 10 // at rest in the database, regardless of // what value is actually present in the // insert call. - let actual = Autoinc::insert(Autoinc { autoinc_field: 23 }) + let actual = ctx.db.autoinc().insert(Autoinc { autoinc_field: 23 }) assert_eq!(actual.autoinc_field, i); } } -#[spacetimedb(reducer)] -fn insert_id() { +#[reducer] +fn insert_id(ctx: &ReducerContext) { for _ in 0..10 { // These also will have values of 1, 2, ..., 10. // There's no collision and silent failure to insert, // because the value of the field is ignored and overwritten // with the automatically incremented value. - Identity::insert(Identity { id_field: 23 }) + ctx.db.identity().insert(Identity { id_field: 23 }) } } ``` @@ -414,7 +414,7 @@ fn insert_id() { Given a table, we can iterate over all the rows in it. ```rust -#[spacetimedb(table(public))] +#[table(name = person, public)] struct Person { #[unique] id: u64, @@ -425,20 +425,20 @@ struct Person { } ``` -// Every table structure an iter function, like: +// Every table structure has a generated iter function, like: ```rust -fn MyTable::iter() -> TableIter +ctx.db.my_table().iter() ``` `iter()` returns a regular old Rust iterator, giving us a sequence of `Person`. The database sends us over rows, one at a time, for each time through the loop. This means we get them by value, and own the contents of `String` fields and so on. ``` -#[spacetimedb(reducer)] -fn iteration() { +#[reducer] +fn iteration(ctx: &ReducerContext) { let mut addresses = HashSet::new(); - for person in Person::iter() { + for person in ctx.db.person().iter() { addresses.insert(person.address); } @@ -457,9 +457,9 @@ Our `Person` table has a unique id column, so we can filter for a row matching t The name of the filter method just corresponds to the column name. ```rust -#[spacetimedb(reducer)] -fn filtering(id: u64) { - match Person::find_by_id(&id) { +#[reducer] +fn filtering(ctx: &ReducerContext, id: u64) { + match ctx.db.person().id().find(id) { Some(person) => println!("Found {person}"), None => println!("No person with id {id}"), } @@ -469,9 +469,9 @@ fn filtering(id: u64) { Our `Person` table also has a column for age. Unlike IDs, ages aren't unique. Filtering for every person who is 21, then, gives us an `Iterator` rather than an `Option`. ```rust -#[spacetimedb(reducer)] -fn filtering_non_unique() { - for person in Person::find_by_age(&21) { +#[reducer] +fn filtering_non_unique(ctx: &ReducerContext) { + for person in ctx.db.person().age().find(21) { println!("{person} has turned 21"); } } @@ -482,9 +482,9 @@ fn filtering_non_unique() { Like filtering, we can delete by a unique column instead of the entire row. ```rust -#[spacetimedb(reducer)] -fn delete_id(id: u64) { - Person::delete_by_id(&id) +#[reducer] +fn delete_id(ctx: &ReducerContext, id: u64) { + ctx.db.person().id().delete(id) } ```