From cc5115a65e6379f92bee4df5e284d6a00ecb1da7 Mon Sep 17 00:00:00 2001 From: James Gilles Date: Tue, 10 Dec 2024 16:12:03 -0500 Subject: [PATCH 1/2] Start rewriting Rust reference --- docs/modules/rust/index.md | 98 +++++++++++++++++++++++++++----------- 1 file changed, 71 insertions(+), 27 deletions(-) diff --git a/docs/modules/rust/index.md b/docs/modules/rust/index.md index 24fa82b..e50d67d 100644 --- a/docs/modules/rust/index.md +++ b/docs/modules/rust/index.md @@ -1,23 +1,87 @@ + + # SpacetimeDB Rust Modules -Rust clients of SpacetimeDB use the [Rust SpacetimeDB module library][module library] to write modules which interact with the SpacetimeDB database. +SpacetimeDB allows using the Rust language to write server-side applications. These applications are called **modules** and have access to a built-in database. + +Rust modules are written with the [Rust module library][module library]. They are built using [cargo](https://doc.rust-lang.org/cargo/), like any other Rust application, and deployed using the [`spacetime` CLI tool](/install). Rust modules can import any Rust [crate](https://crates.io/) that supports being compiled to WebAssembly. + +This reference assumes you are familiar with the basics of Rust. If you aren't, check out Rust's [excellent documentation](https://www.rust-lang.org/learn). + +## Overview + +SpacetimeDB modules have two ways to interact with the outside world. They can: + +- Declare [tables](#tables), which are exactly like tables in a SQL database. +- Declare [reducers](#reducers), which are public functions that can be invoked by [clients](../../#client). + +These declarations are written using ordinary Rust code, annotated with special macros. Declarations can use any type deriving the [`SpacetimeType`](#spacetimetype) trait. + +Under the hood, SpacetimeDB modules import a [specific WebAssembly ABI](../../webassembly-abi/) and export a small number of special functions. This is automatically handled by the `table` and `reducer` macros. + +Reducers have access to a special [`ReducerContext`](#reducercontext) argument. This argument allows reading and writing the database attached to a module. It also provides some additional functionality, like generating random numbers and scheduling future operations. + +The module library has built-in support for the [log crate](https://docs.rs/log/latest/log/index.html). All modules automatically install a suitable logger when they are first loaded by SpacetimeDB. Log macros can be used anywhere in module code, and the module's log output can be inspected using the `spacetime logs` command. + +## Tables + +Tables are declared using the [`#[table]` macro](https://docs.rs/spacetimedb/latest/spacetimedb/attr.table.html). + + +## Reducers -First, the `spacetimedb` library provides a number of macros for creating tables and Rust `struct`s corresponding to rows in those tables. +Reducers are declared using the [`#[reducer]` macro](https://docs.rs/spacetimedb/latest/spacetimedb/attr.reducer.html). -Then the client API allows interacting with the database inside special functions called reducers. +### Life cycle annotations -This guide assumes you are familiar with some basics of Rust. At the very least, you should be familiar with the idea of using attribute macros. An extremely common example is `derive` macros. +## `SpacetimeType` -Derive macros look at the type they are attached to and generate some related code. In this example, `#[derive(Debug)]` generates the formatting code needed to print out a `Location` for debugging purposes. +Any Rust type implementing the [`SpacetimeType` trait](https://docs.rs/spacetimedb/latest/spacetimedb/trait.SpacetimeType.html) can be used in table and reducer declarations. A derive macro is provided, and can be used on both structs and enums: ```rust -#[derive(Debug)] +use spacetimedb::SpacetimeType; + +#[derive(SpacetimeType)] struct Location { x: u32, - y: u32, + y: u32 +} + +#[derive(SpacetimeType)] +enum FruitCrate { + Bananas { count: u32, freshness: u32 }, + Plastic { count: u32 } } ``` +The fields of the struct/enum must also implement `SpacetimeType`. + +SpacetimeType is implemented for many of the primitive types in the standard library: + +- `bool` +- `u8`, `u16`, `u32`, `u64`, `u128` +- `i8`, `i16`, `i32`, `i64`, `i128` +- `f32`, `f64` + +And common data structures: + +- `String` and `&str`, utf-8 string data +- `()`, the unit type +- `Option where T: SpacetimeType` +- `Vec where T: SpacetimeType` + +(Storing collections in database tables is a form of [denormalization](https://en.wikipedia.org/wiki/Denormalization).) + +All `#[table(..)]` types automatically derive `SpacetimeType`. + +Types deriving `SpacetimeType` also automatically derive the [`Serialize`](https://docs.rs/spacetimedb/latest/spacetimedb/trait.Serialize.html) and [`Deserialize`](https://docs.rs/spacetimedb/latest/spacetimedb/trait.Deserialize.html) traits, as well as the [`std::Debug`](https://doc.rust-lang.org/std/fmt/trait.Debug.html) trait. (There are currently no trait bounds on `SpacetimeType` documenting this fact.) + +The `Serialize` and `Deserialize` traits are used to convert Rust data structures to other formats, suitable for storing on disk or passing over the network. `SpacetimeType` + +## `ReducerContext` + +The `ReducerContext` + ## SpacetimeDB Macro basics 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. @@ -92,8 +156,6 @@ The `#[table(name = table_name)]` macro is applied to a Rust struct with named f By default, tables are considered **private**. This means that they are only readable by the table owner, and by 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 #[table(name = my_table, public)] struct MyTable { @@ -104,24 +166,6 @@ struct MyTable { 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. - -This is automatically defined for built in numeric types: - -- `bool` -- `u8`, `u16`, `u32`, `u64`, `u128` -- `i8`, `i16`, `i32`, `i64`, `i128` -- `f32`, `f64` - -And common data structures: - -- `String` and `&str`, utf-8 string data -- `()`, the unit type -- `Option where T: SpacetimeType` -- `Vec where T: SpacetimeType` - -All `#[table(..)]` types are `SpacetimeType`s, and accordingly, all of their fields have to be. - ```rust #[table(name = another_table, public)] struct AnotherTable { From ad3026cdb9cab572e545da81bbe6a9cd2d407297 Mon Sep 17 00:00:00 2001 From: James Gilles Date: Tue, 17 Dec 2024 16:04:24 -0500 Subject: [PATCH 2/2] Move rust reference to rustdoc --- docs/modules/rust/index.md | 557 +------------------------------------ 1 file changed, 3 insertions(+), 554 deletions(-) diff --git a/docs/modules/rust/index.md b/docs/modules/rust/index.md index e50d67d..1e508e5 100644 --- a/docs/modules/rust/index.md +++ b/docs/modules/rust/index.md @@ -1,558 +1,7 @@ - - -# SpacetimeDB Rust Modules +# Rust Module SDK Reference SpacetimeDB allows using the Rust language to write server-side applications. These applications are called **modules** and have access to a built-in database. -Rust modules are written with the [Rust module library][module library]. They are built using [cargo](https://doc.rust-lang.org/cargo/), like any other Rust application, and deployed using the [`spacetime` CLI tool](/install). Rust modules can import any Rust [crate](https://crates.io/) that supports being compiled to WebAssembly. - -This reference assumes you are familiar with the basics of Rust. If you aren't, check out Rust's [excellent documentation](https://www.rust-lang.org/learn). - -## Overview - -SpacetimeDB modules have two ways to interact with the outside world. They can: - -- Declare [tables](#tables), which are exactly like tables in a SQL database. -- Declare [reducers](#reducers), which are public functions that can be invoked by [clients](../../#client). - -These declarations are written using ordinary Rust code, annotated with special macros. Declarations can use any type deriving the [`SpacetimeType`](#spacetimetype) trait. - -Under the hood, SpacetimeDB modules import a [specific WebAssembly ABI](../../webassembly-abi/) and export a small number of special functions. This is automatically handled by the `table` and `reducer` macros. - -Reducers have access to a special [`ReducerContext`](#reducercontext) argument. This argument allows reading and writing the database attached to a module. It also provides some additional functionality, like generating random numbers and scheduling future operations. - -The module library has built-in support for the [log crate](https://docs.rs/log/latest/log/index.html). All modules automatically install a suitable logger when they are first loaded by SpacetimeDB. Log macros can be used anywhere in module code, and the module's log output can be inspected using the `spacetime logs` command. - -## Tables - -Tables are declared using the [`#[table]` macro](https://docs.rs/spacetimedb/latest/spacetimedb/attr.table.html). - - -## Reducers - -Reducers are declared using the [`#[reducer]` macro](https://docs.rs/spacetimedb/latest/spacetimedb/attr.reducer.html). - -### Life cycle annotations - -## `SpacetimeType` - -Any Rust type implementing the [`SpacetimeType` trait](https://docs.rs/spacetimedb/latest/spacetimedb/trait.SpacetimeType.html) can be used in table and reducer declarations. A derive macro is provided, and can be used on both structs and enums: - -```rust -use spacetimedb::SpacetimeType; - -#[derive(SpacetimeType)] -struct Location { - x: u32, - y: u32 -} - -#[derive(SpacetimeType)] -enum FruitCrate { - Bananas { count: u32, freshness: u32 }, - Plastic { count: u32 } -} -``` - -The fields of the struct/enum must also implement `SpacetimeType`. - -SpacetimeType is implemented for many of the primitive types in the standard library: - -- `bool` -- `u8`, `u16`, `u32`, `u64`, `u128` -- `i8`, `i16`, `i32`, `i64`, `i128` -- `f32`, `f64` - -And common data structures: - -- `String` and `&str`, utf-8 string data -- `()`, the unit type -- `Option where T: SpacetimeType` -- `Vec where T: SpacetimeType` - -(Storing collections in database tables is a form of [denormalization](https://en.wikipedia.org/wiki/Denormalization).) - -All `#[table(..)]` types automatically derive `SpacetimeType`. - -Types deriving `SpacetimeType` also automatically derive the [`Serialize`](https://docs.rs/spacetimedb/latest/spacetimedb/trait.Serialize.html) and [`Deserialize`](https://docs.rs/spacetimedb/latest/spacetimedb/trait.Deserialize.html) traits, as well as the [`std::Debug`](https://doc.rust-lang.org/std/fmt/trait.Debug.html) trait. (There are currently no trait bounds on `SpacetimeType` documenting this fact.) - -The `Serialize` and `Deserialize` traits are used to convert Rust data structures to other formats, suitable for storing on disk or passing over the network. `SpacetimeType` - -## `ReducerContext` - -The `ReducerContext` - -## SpacetimeDB Macro basics - -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: -// |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}; - -// 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. -#[table(name = person, public)] -pub struct Person { - name: String, -} - -// 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. -#[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: - 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 recognizes their types. Reducers also have to be top level -// functions, not methods. -#[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 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 - // the std version, excepted it is redirected out to the module's logs. - println!("Hello, {}!", person.name); - } - println!("Hello, World!"); -} - -// Reducers can't return values, but can return errors. To do so, -// 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. -#[reducer] -pub fn add_person(ctx: &ReducerContext, name: String) -> Result<(), String> { - if name.is_empty() { - return Err("Name cannot be empty"); - } - - ctx.db.person().insert(Person { name }) -} -``` - -## Macro API - -Now we'll get into details on all the macro APIs SpacetimeDB provides, starting with all the variants of the `spacetimedb` attribute. - -### Defining tables - -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 `#[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. - -```rust -#[table(name = my_table, public)] -struct MyTable { - field1: String, - field2: u32, -} -``` - -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. - -```rust -#[table(name = another_table, public)] -struct AnotherTable { - // Fine, some builtin types. - id: u64, - name: Option, - - // Fine, another table type. - table: Table, - - // Fine, another type we explicitly make serializable. - serial: Serial, -} -``` - -If you want to have a field that is not one of the above primitive types, and not a table of its own, you can derive the `SpacetimeType` attribute on it. - -We can derive `SpacetimeType` on `struct`s and `enum`s with members that are themselves `SpacetimeType`s. - -```rust -#[derive(SpacetimeType)] -enum Serial { - Builtin(f64), - Compound { - s: String, - bs: Vec, - } -} -``` - -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 -#[table(name = person, public)] -struct Person { - #[unique] - id: u64, - - name: String, - address: String, -} -``` - -### Defining reducers - -`#[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 -#[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 = ctx.db.item().item_id().find(id).ok_or(GameErr::InvalidId)?; - item.owner = Some(player_id); - ctx.db.item().item_id().update(item); - Ok(()) -} - -#[table(name = item, public)] -struct Item { - #[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. `#[primary_key]`, `#[unique]` and `#[autoinc]` are covered below, describing how those attributes affect the semantics of inserting, filtering, and so on. - -#[SpacetimeType] - -#[sats] - -### Defining Scheduler Tables - -Tables can be used to schedule a reducer calls either at a specific timestamp or at regular intervals. - -```rust -// The `scheduled` attribute links this table to a reducer. -#[table(name = send_message_timer, scheduled(send_message)] -struct SendMessageTimer { - text: String, -} -``` - -The `scheduled` attribute adds a couple of default fields and expands as follows: - -```rust -#[table(name = send_message_timer, scheduled(send_message)] - struct SendMessageTimer { - text: String, // original field - #[primary_key] - #[autoinc] - scheduled_id: u64, // identifier for internal purpose - scheduled_at: ScheduleAt, //schedule details -} - -pub enum ScheduleAt { - /// A specific time at which the reducer is scheduled. - /// Value is a UNIX timestamp in microseconds. - Time(u64), - /// A regular interval at which the repeated reducer is scheduled. - /// Value is a duration in microseconds. - Interval(u64), -} -``` - -Managing timers with a scheduled table is as simple as inserting or deleting rows from the table. - -```rust -#[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> { - // ... -} - -// Scheduling reducers inside `init` reducer -#[reducer(init)] -fn init(ctx: &ReducerContext) { - // Scheduling a reducer for a specific Timestamp - 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`. - scheduled_at: ctx.timestamp.plus(Duration::from_secs(10)).into() - }); - - // Scheduling a reducer to be called at fixed interval of 100 milliseconds. - 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`. - scheduled_at: duration!(100ms).into(), - }); -} -``` - -## Client API - -Besides the macros for creating tables and reducers, there's two other parts of the Rust SpacetimeDB library. One is a collection of macros for logging, and the other is all the automatically generated functions for operating on those tables. - -### `println!` and friends - -Because reducers run in a WASM sandbox, they don't have access to general purpose I/O from the Rust standard library. There's no filesystem or network access, and no input or output. This means no access to things like `std::println!`, which prints to standard output. - -SpacetimeDB modules have access to logging output. These are exposed as macros, just like their `std` equivalents. The names, and all the Rust formatting machinery, work the same; just the location of the output is different. - -Logs for a module can be viewed with the `spacetime logs` command from the CLI. - -```rust -use spacetimedb::{ - println, - print, - eprintln, - eprint, - dbg, -}; - -#[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"); - - // These log at log::Level::Error. - eprint!("Oops..."); - eprintln!(", we hit an error"); - - // Just like std::dbg!, this prints its argument and returns the value, - // as a drop-in way to print expressions. So this will print out |i| - // before passing the value of |i| along to the calling function. - // - // The output is logged log::Level::Debug. - ctx.db.outputted_number().insert(dbg!(i)); -} -``` - -### Generated functions on a SpacetimeDB table - -We'll work off these structs to see what functions SpacetimeDB generates: - -This table has a plain old column. - -```rust -#[table(name = ordinary, public)] -struct Ordinary { - ordinary_field: u64, -} -``` - -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 -#[table(name = unique, public)] -struct Unique { - // A unique column: - #[unique] - unique_field: u64, -} -``` - -This table has an automatically incrementing column. SpacetimeDB automatically provides an incrementing sequence of values for this field, and sets the field to that value when you insert the row. - -Only integer types can be `#[unique]`: `u8`, `u16`, `u32`, `u64`, `u128`, `i8`, `i16`, `i32`, `i64` and `i128`. - -```rust -#[table(name = autoinc, public)] -struct Autoinc { - #[autoinc] - autoinc_field: u64, -} -``` - -These attributes can be combined, to create an automatically assigned ID usable for filtering. - -```rust -#[table(name = identity, public)] -struct Identity { - #[autoinc] - #[unique] - id_field: u64, -} -``` - -### Insertion - -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 `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 -#[reducer] -fn insert_ordinary(ctx: &ReducerContext, value: u64) { - let ordinary = Ordinary { ordinary_field: value }; - let result = ctx.db.ordinary().insert(ordinary); - assert_eq!(ordinary.ordinary_field, result.ordinary_field); -} -``` - -When there is a unique column constraint on the table, insertion can fail if a uniqueness constraint is violated. - -If we insert two rows which have the same value of a unique column, the second will fail. - -```rust -#[reducer] -fn insert_unique(ctx: &ReducerContext, value: u64) { - let result = ctx.db.unique().insert(Unique { unique_field: value }); - assert!(result.is_ok()); - - let result = ctx.db.unique().insert(Unique { unique_field: value }); - assert!(result.is_err()); -} -``` - -When inserting a table with an `#[autoinc]` column, the database will automatically overwrite whatever we give it with an atomically increasing value. - -The returned row has the `autoinc` column set to the value that was actually written into the database. - -```rust -#[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 = ctx.db.autoinc().insert(Autoinc { autoinc_field: 23 }) - assert_eq!(actual.autoinc_field, i); - } -} - -#[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. - ctx.db.identity().insert(Identity { id_field: 23 }) - } -} -``` - -### Iterating - -Given a table, we can iterate over all the rows in it. - -```rust -#[table(name = person, public)] -struct Person { - #[unique] - id: u64, - - #[index(btree)] - age: u32, - name: String, - address: String, -} -``` - -// Every table structure has a generated iter function, like: - -```rust -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. - -``` -#[reducer] -fn iteration(ctx: &ReducerContext) { - let mut addresses = HashSet::new(); - - for person in ctx.db.person().iter() { - addresses.insert(person.address); - } - - for address in addresses.iter() { - println!("{address}"); - } -} -``` - -### Filtering - -Often, we don't need to look at the entire table, and instead are looking for rows with specific values in certain columns. - -Our `Person` table has a unique id column, so we can filter for a row matching that ID. Since it is unique, we will find either 0 or 1 matching rows in the database. This gets represented naturally as an `Option` in Rust. SpacetimeDB automatically creates and uses indexes for filtering on unique columns, so it is very efficient. - -The name of the filter method just corresponds to the column name. - -```rust -#[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}"), - } -} -``` - -Our `Person` table also has an index on its `age` column. Unlike IDs, ages aren't unique. Filtering for every person who is 21, then, gives us an `Iterator` rather than an `Option`. - -```rust -#[reducer] -fn filtering_non_unique(ctx: &ReducerContext) { - for person in ctx.db.person().age().filter(21u32) { - println!("{} has turned 21", person.name); - } -} -``` - -> NOTE: An unfortunate interaction between Rust's trait solver and integer literal defaulting rules means that you must specify the types of integer literals passed to `filter` and `find` methods via the suffix syntax, like `21u32`. If you don't, you'll see a compiler error like: -> ``` -> error[E0271]: type mismatch resolving `::Column == u32` -> --> modules/rust-wasm-test/src/lib.rs:356:48 -> | -> 356 | for person in ctx.db.person().age().filter(21) { -> | ------ ^^ expected `u32`, found `i32` -> | | -> | required by a bound introduced by this call -> | -> = note: required for `i32` to implement `BTreeIndexBounds<(u32,), SingleBound>` -> note: required by a bound in `BTreeIndex::::filter` -> | -> 410 | pub fn filter(&self, b: B) -> impl Iterator -> | ------ required by a bound in this associated function -> 411 | where -> 412 | B: BTreeIndexBounds, -> | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `BTreeIndex::::filter` -> ``` - -### Deleting - -Like filtering, we can delete by an indexed or unique column instead of the entire row. - -```rust -#[reducer] -fn delete_id(ctx: &ReducerContext, id: u64) { - ctx.db.person().id().delete(id) -} -``` +Rust modules are written with the the Rust Module SDK. They are built using [cargo](https://doc.rust-lang.org/cargo/), like any other Rust application, and deployed using the [`spacetime` CLI tool](https://spacetimedb.com/install). Rust modules can import any Rust [crate](https://crates.io/) that supports being compiled to WebAssembly. -[macro library]: https://github.com/clockworklabs/SpacetimeDB/tree/master/crates/bindings-macro -[module library]: https://github.com/clockworklabs/SpacetimeDB/tree/master/crates/lib -[demo]: /#demo +The Rust module SDK reference documentation is automatically generated from the module's source code and can be found [here](https://docs.rs/spacetimedb/latest/spacetimedb/).