Skip to content
Open
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
40 changes: 33 additions & 7 deletions crates/bindings-macro/src/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,17 @@ struct Column<'a> {
default_value: Option<syn::Expr>,
}

fn is_string_type(ty: &syn::Type) -> bool {
let syn::Type::Path(type_path) = ty else {
return false;
};
type_path
.path
.segments
.last()
.is_some_and(|segment| segment.ident == "String")
}

fn try_find_column<'a, 'b, T: ?Sized>(cols: &'a [Column<'b>], name: &T) -> Option<&'a Column<'b>>
where
Ident: PartialEq<T>,
Expand Down Expand Up @@ -1013,10 +1024,17 @@ pub(crate) fn table_impl(mut args: TableArgs, item: &syn::DeriveInput) -> syn::R
if let Some(val) = &col.default_value {
let ty = &col.ty;
let ident_span = col.ident.span();
Some(quote_spanned! { ident_span =>

if is_string_type(ty) {
Some(quote_spanned! { ident_span =>
let _check: &'static str = #val;
})
} else {
// This closure enforces that `val` is of type `ty` at compile-time.
let _check: #ty = #val;
})
Some(quote_spanned! { ident_span =>
let _check: #ty = #val;
})
}
} else {
None
}
Expand All @@ -1027,13 +1045,21 @@ pub(crate) fn table_impl(mut args: TableArgs, item: &syn::DeriveInput) -> syn::R
if let Some(val) = &col.default_value {
let col_id = col.index;
let ty = &col.ty;
let value = if is_string_type(ty) {
quote! {
let value: &'static str = #val;
value.serialize(spacetimedb::sats::algebraic_value::ser::ValueSerializer).expect("default value serialization failed")
}
} else {
quote! {
let value: #ty = #val;
value.serialize(spacetimedb::sats::algebraic_value::ser::ValueSerializer).expect("default value serialization failed")
}
};
Some(quote! {
spacetimedb::table::ColumnDefault {
col_id: #col_id,
value: {
let value: #ty = #val;
value.serialize(spacetimedb::sats::algebraic_value::ser::ValueSerializer).expect("default value serialization failed")
},
value: { #value },
},
})
} else {
Expand Down
7 changes: 3 additions & 4 deletions crates/smoketests/tests/smoketests/column_defaults.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ const EXPECTED_DEFAULTS: &[(&str, &str)] = &[
("f32_negative_value", "-32.5"),
("f64_positive_value", "64.25"),
("f64_negative_value", "-64.25"),
// TODO: uncomment this once string default values are fixed in Rust
//("string_value", r#""default string""#),
("string_value", r#""default string""#),
];

fn test_defaults(test: &mut Smoketest, publish_updated: impl FnOnce(&mut Smoketest)) {
Expand Down Expand Up @@ -134,8 +133,8 @@ pub struct DefaultsTestTable {
pub f64_positive_value: f64,
#[default(-64.25)]
pub f64_negative_value: f64,
// #[default("default string")]
// pub string_value: String,
#[default("default string")]
pub string_value: String,
}
"#;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,6 @@ pub struct Player {

The `#[default(value)]` attribute specifies the default value. The expression must be const-evaluable (usable in a `const` context).

:::note Rust Limitation
Default values in Rust must be const-evaluable. This means you **cannot** use `String` defaults like `#[default("".to_string())]` because `.to_string()` is not a const fn. Only primitive types, enums, and other const-constructible types can have defaults.
:::

</TabItem>
<TabItem value="cpp" label="C++">

Expand Down
Loading