Reputation: 37095
I have a function that takes two parameters (let's say two strings):
fn foo(x: String, y: String) -> String {
x + y
}
I always know x
at compile-time, but I do not know y
until run-time.
How can I write this for maximum efficiency, without copy-pasting the function for each x
?
Upvotes: 1
Views: 754
Reputation: 30032
Note that your function foo
currently requires two heap-allocated strings unnecessarily. Here is another version, which is more versatile and efficient (although YMMV as I will describe next):
fn foo<T>(x: T, y: &str) -> String
where
T: Into<String>,
{
x.into() + y
}
assert_eq!(foo("mobile ", "phones"), "mobile phones");
Concatenation will almost always require a memory allocation somewhere, but this one can take heap-allocated strings as well as arbitrary string slices.
It can also avoid a reallocation if the capacity of x
is large enough, although this is not very likely to be the case, considering that x
is obtained from a string known at compile time. String::insert_str
would have allowed us to revert the position of the type parameter, but an insertion at the front of the string has an O(n) cost. Knowing the first operand of a string concatenation a priori is not very beneficial to the compiler in terms of what optimizations it can employ.
Let us assume that we still want to perform a partial function at compile time. This seems to be another use case where const generics would shine. With this feature, one could indeed monomorphize this function over a &'static str
. As of nightly-2022-06-29
, being able to use a &'static str
as a const parameter is still unstable, but the code below compiles and works as intended. This feature is tracked in issue 95174.
#![feature(adt_const_params)]
fn foo<const X: &'static str>(y: &str) -> String {
X.to_string() + y
}
let s = "string".to_string();
println!("{}", foo::<"I am ">(&s));
Alas, const generics applied to string slices are still unstable, and not quite ready for this yet. Albeit less ergonomic, we can instead replicate the effect of instancing one function for each string literal with rule-based macros:
macro_rules! define_foo {
($fname: ident, $x: literal) => {
fn $fname (y: &str) -> String {
$x.to_string() + y
}
}
}
Using:
define_foo!(bar, "Conan ");
assert_eq!(bar("Osíris"), "Conan Osíris");
See also:
Upvotes: 6
Reputation: 10237
I was able to do it in nightly using a const function returning closure:
#![feature(const_fn)]
fn foo(x: String, y: String) -> String {
x + &y
}
const fn foo_applied(x: String) -> impl Fn(String) -> String {
move |y| foo(x.clone(), y)
}
fn main() {
let foo_1 = foo_applied("1 ".into());
println!("{}", foo_1("2".into()));
let foo_2 = foo_applied("2 ".into());
println!("{}", foo_2("1".into()));
}
Upvotes: 2