Skip to content

Commit 49d5cd1

Browse files
committed
Add BytesOut and StringOut typed output wrappers
Avoid extra allocations by returning typed ClonedVar wrappers directly. The macro detects these types and extracts the inner ClonedVar without calling .into() on intermediate allocations.
1 parent 7838d21 commit 49d5cd1

File tree

4 files changed

+94
-19
lines changed

4 files changed

+94
-19
lines changed

shards/modules/core/src/uuid.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ use shards::shard::LegacyShard;
77
use shards::shard::Shard;
88
use shards::simple_shard;
99

10+
use shards::types::BytesOut;
1011
use shards::types::ClonedVar;
1112
use shards::types::Context;
1213
use shards::types::ExposedTypes;
1314
use shards::types::InstanceData;
1415
use shards::types::OptionalString;
16+
use shards::types::StringOut;
1517
use shards::types::BYTES_OR_STRING_TYPES;
1618
use shards::types::INT_TYPES;
1719

@@ -38,28 +40,28 @@ fn uuid_to_string(
3840
input: [u8; 16],
3941
#[param("Hyphenated", "Whether to use hyphens in the output.", default = false)]
4042
hyphenated: bool,
41-
) -> String {
43+
) -> StringOut {
4244
let uuid = uuid::Uuid::from_bytes(input);
4345
if hyphenated {
44-
uuid.hyphenated().to_string()
46+
uuid.hyphenated().to_string().as_str().into()
4547
} else {
46-
uuid.simple().to_string()
48+
uuid.simple().to_string().as_str().into()
4749
}
4850
}
4951

5052
#[simple_shard("UUID.ToBytes", "Reads a UUID and formats it into bytes.")]
51-
fn uuid_to_bytes(input: [u8; 16]) -> Vec<u8> {
52-
input.to_vec()
53+
fn uuid_to_bytes(input: [u8; 16]) -> BytesOut {
54+
input.as_slice().into()
5355
}
5456

5557
#[simple_shard("NanoID", "Creates a random NanoID.")]
5658
fn nanoid_create(
5759
_: (),
5860
#[param("Size", "The output string length of the created NanoID.", default = 21i64)]
5961
size: i64,
60-
) -> String {
62+
) -> StringOut {
6163
let size = size as usize;
62-
nanoid::nanoid!(size)
64+
nanoid::nanoid!(size).as_str().into()
6365
}
6466

6567
// Legacy shard for UUID.Convert (handles multiple input types)

shards/rust/src/types/common.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,54 @@ pub type RawString = SHString;
212212
#[derive(Default, Serialize, Hash, PartialEq, Eq, PartialOrd, Ord)]
213213
pub struct ClonedVar(pub Var);
214214

215+
/// Typed wrapper for bytes output - avoids extra allocations
216+
#[repr(transparent)]
217+
#[derive(Default)]
218+
pub struct BytesOut(pub ClonedVar);
219+
220+
/// Typed wrapper for string output - avoids extra allocations
221+
#[repr(transparent)]
222+
#[derive(Default)]
223+
pub struct StringOut(pub ClonedVar);
224+
225+
impl BytesOut {
226+
pub fn new(data: &[u8]) -> Self {
227+
BytesOut(Var::from(data).into())
228+
}
229+
}
230+
231+
impl From<&[u8]> for BytesOut {
232+
fn from(v: &[u8]) -> Self {
233+
BytesOut::new(v)
234+
}
235+
}
236+
237+
impl std::ops::Deref for BytesOut {
238+
type Target = ClonedVar;
239+
fn deref(&self) -> &Self::Target {
240+
&self.0
241+
}
242+
}
243+
244+
impl StringOut {
245+
pub fn new(s: &str) -> Self {
246+
StringOut(Var::ephemeral_string(s).into())
247+
}
248+
}
249+
250+
impl From<&str> for StringOut {
251+
fn from(v: &str) -> Self {
252+
StringOut::new(v)
253+
}
254+
}
255+
256+
impl std::ops::Deref for StringOut {
257+
type Target = ClonedVar;
258+
fn deref(&self) -> &Self::Target {
259+
&self.0
260+
}
261+
}
262+
215263
impl Ord for Var {
216264
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
217265
unsafe {

shards/rust/src/types/shard_type.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,16 @@ impl ShardType for SHColor {
161161
fn shards_types() -> &'static Types { &COLOR_TYPES }
162162
fn shards_var_type() -> Type { common_type::color_var }
163163
}
164+
165+
// Typed output wrappers
166+
impl ShardType for super::BytesOut {
167+
fn shards_type() -> Type { common_type::bytes }
168+
fn shards_types() -> &'static Types { &BYTES_TYPES }
169+
fn shards_var_type() -> Type { common_type::bytes_var }
170+
}
171+
172+
impl ShardType for super::StringOut {
173+
fn shards_type() -> Type { common_type::string }
174+
fn shards_types() -> &'static Types { &STRING_TYPES }
175+
fn shards_var_type() -> Type { common_type::string_var }
176+
}

shards/rust_macro/src/lib.rs

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1145,30 +1145,37 @@ fn generate_simple_shard(args: SimpleShardAttrArgs, func: syn::ItemFn) -> Result
11451145

11461146
// Get output type from return type
11471147
let mut returns_result = false;
1148+
let mut returns_typed_out = false; // BytesOut, StringOut, etc.
11481149
let output_type = match &func.sig.output {
11491150
syn::ReturnType::Type(_, ty) => {
11501151
// Handle Result<T, _> wrapper
11511152
if let syn::Type::Path(path) = ty.as_ref() {
1152-
if path
1153-
.path
1154-
.segments
1155-
.last()
1156-
.map(|s| s.ident == "Result")
1157-
.unwrap_or(false)
1158-
{
1153+
let type_name = path.path.segments.last().map(|s| s.ident.to_string());
1154+
1155+
if type_name.as_deref() == Some("Result") {
11591156
returns_result = true;
11601157
// Extract T from Result<T, E>
11611158
if let syn::PathArguments::AngleBracketed(args) =
11621159
&path.path.segments.last().unwrap().arguments
11631160
{
11641161
if let Some(syn::GenericArgument::Type(t)) = args.args.first() {
1162+
// Check if inner type is BytesOut/StringOut
1163+
if let syn::Type::Path(inner_path) = t {
1164+
let inner_name = inner_path.path.segments.last().map(|s| s.ident.to_string());
1165+
if matches!(inner_name.as_deref(), Some("BytesOut") | Some("StringOut")) {
1166+
returns_typed_out = true;
1167+
}
1168+
}
11651169
t.clone()
11661170
} else {
11671171
return Err("Invalid Result type".into());
11681172
}
11691173
} else {
11701174
return Err("Invalid Result type".into());
11711175
}
1176+
} else if matches!(type_name.as_deref(), Some("BytesOut") | Some("StringOut")) {
1177+
returns_typed_out = true;
1178+
ty.as_ref().clone()
11721179
} else {
11731180
ty.as_ref().clone()
11741181
}
@@ -1259,7 +1266,7 @@ fn generate_simple_shard(args: SimpleShardAttrArgs, func: syn::ItemFn) -> Result
12591266
.filter(|p| p.is_var)
12601267
.map(|p| {
12611268
let name = &p.rust_name;
1262-
quote! { self.#name.cleanup(Some(context)); }
1269+
quote! { self.#name.cleanup(context); }
12631270
})
12641271
.collect();
12651272

@@ -1382,6 +1389,13 @@ fn generate_simple_shard(args: SimpleShardAttrArgs, func: syn::ItemFn) -> Result
13821389
},
13831390
};
13841391

1392+
// Generate output assignment - typed outputs (BytesOut, StringOut) are already ClonedVar wrappers
1393+
let output_assignment = if returns_typed_out {
1394+
quote! { self.output = result.0; }
1395+
} else {
1396+
quote! { self.output = result.into(); }
1397+
};
1398+
13851399
// Generate compose calls for param_var parameters
13861400
let has_var_params = params.iter().any(|p| p.is_var);
13871401
let param_var_composes: Vec<_> = params
@@ -1500,9 +1514,7 @@ fn generate_simple_shard(args: SimpleShardAttrArgs, func: syn::ItemFn) -> Result
15001514
}
15011515

15021516
fn cleanup(&mut self, context: std::option::Option<&shards::types::Context>) -> std::result::Result<(), &str> {
1503-
if let Some(context) = context {
1504-
#(#param_cleanups)*
1505-
}
1517+
#(#param_cleanups)*
15061518
self.output = shards::types::ClonedVar::default();
15071519
Ok(())
15081520
}
@@ -1517,7 +1529,7 @@ fn generate_simple_shard(args: SimpleShardAttrArgs, func: syn::ItemFn) -> Result
15171529
#(#param_extractions)*
15181530

15191531
let result = #activate_call;
1520-
self.output = result.into();
1532+
#output_assignment
15211533
Ok(Some(self.output.0))
15221534
}
15231535
}

0 commit comments

Comments
 (0)