diff --git a/crates/bindings-macro/src/table.rs b/crates/bindings-macro/src/table.rs index f25e044358c..7877a66dcfd 100644 --- a/crates/bindings-macro/src/table.rs +++ b/crates/bindings-macro/src/table.rs @@ -741,6 +741,17 @@ struct Column<'a> { default_value: Option, } +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, @@ -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 } @@ -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 { diff --git a/crates/smoketests/tests/smoketests/column_defaults.rs b/crates/smoketests/tests/smoketests/column_defaults.rs index 83af4e28afa..258cd6507cf 100644 --- a/crates/smoketests/tests/smoketests/column_defaults.rs +++ b/crates/smoketests/tests/smoketests/column_defaults.rs @@ -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)) { @@ -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, } "#; diff --git a/docs/docs/00200-core-concepts/00300-tables/00250-default-values.md b/docs/docs/00200-core-concepts/00300-tables/00250-default-values.md index ce0ac6c3871..0a45eed057c 100644 --- a/docs/docs/00200-core-concepts/00300-tables/00250-default-values.md +++ b/docs/docs/00200-core-concepts/00300-tables/00250-default-values.md @@ -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. -::: -