From f3a3ac026bec6256208ff5f2a87e3facaa86a60c Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 6 Dec 2024 07:41:28 -0800 Subject: [PATCH] [Rust] Look through typedefs to pass borrowed strings as `&str` (#1102) wit-bindgen already knows to special-case passing strings by reference and pass them as `&str` instead of naively passing them as `&String` and requiring callers to have owned `Strings`. Implement this behavior for type aliases of strings as well, so that in code like this: ```wit type my-string = string; foo: func(x: my-string); ``` the argument is `&str` instead of `&MyString` which is effectively `&String`. And similar for lists. This comes up in several functions in wasi-http; for example, in the bindings for the `Fields::append` function, it enables this change: ```diff @@ -5075,8 +5075,8 @@ pub mod wasi { /// `field-value` are syntactically invalid. pub fn append( &self, - name: &FieldName, - value: &FieldValue, + name: &str, + value: &[u8], ) -> Result<(), HeaderError> { unsafe { #[repr(align(1))] ``` where `FieldName` and `FieldValue` are defined as: ```wit pub type FieldKey = _rt::String; pub type FieldName = FieldKey; pub type FieldValue = _rt::Vec; ``` --- crates/rust/src/interface.rs | 24 ++++++++++++++++ crates/rust/tests/codegen.rs | 54 +++++++++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index d8a601dbc..5c44bcefa 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -1113,6 +1113,30 @@ macro_rules! {macro_name} {{ } fn print_ty(&mut self, ty: &Type, mode: TypeMode) { + // If we have a typedef of a string or a list, the typedef is an alias + // for `String` or `Vec`. If this is a borrow, instead of borrowing + // them as `&String` or `&Vec`, use `&str` or `&[T]` so that callers + // don't need to create owned copies. + if let Type::Id(id) = ty { + let id = dealias(self.resolve, *id); + let typedef = &self.resolve.types[id]; + match &typedef.kind { + TypeDefKind::Type(Type::String) => { + if let Some(lt) = mode.lifetime { + self.print_borrowed_str(lt); + return; + } + } + TypeDefKind::List(element) => { + if mode.lifetime.is_some() { + self.print_list(element, mode); + return; + } + } + _ => {} + } + } + match ty { Type::Id(t) => self.print_tyid(*t, mode), Type::Bool => self.push_str("bool"), diff --git a/crates/rust/tests/codegen.rs b/crates/rust/tests/codegen.rs index b5757b3df..ad2310e3c 100644 --- a/crates/rust/tests/codegen.rs +++ b/crates/rust/tests/codegen.rs @@ -82,6 +82,58 @@ mod strings { } } +/// Like `strings` but with a type alias. +mod aliased_strings { + wit_bindgen::generate!({ + inline: " + package my:strings; + + world not-used-name { + import cat: interface { + type my-string = string; + foo: func(x: my-string); + bar: func() -> my-string; + } + } + ", + }); + + #[allow(dead_code)] + fn test() { + // Test the argument is `&str`. + cat::foo("hello"); + + // Test the return type is `String`. + let _t: String = cat::bar(); + } +} + +/// Like `aliased_string` but with lists instead of strings. +mod aliased_lists { + wit_bindgen::generate!({ + inline: " + package my:strings; + + world not-used-name { + import cat: interface { + type my-list = list; + foo: func(x: my-list); + bar: func() -> my-list; + } + } + ", + }); + + #[allow(dead_code)] + fn test() { + // Test the argument is `&[u8]`. + cat::foo(b"hello"); + + // Test the return type is `Vec`. + let _t: Vec = cat::bar(); + } +} + mod run_ctors_once_workaround { wit_bindgen::generate!({ inline: " @@ -96,7 +148,7 @@ mod run_ctors_once_workaround { }); } -/// Like `strings` but with raw_strings`. +/// Like `strings` but with `raw_strings`. mod raw_strings { wit_bindgen::generate!({ inline: "