1

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?

sdgfsdh
  • 33,689
  • 26
  • 132
  • 245
  • 2
    Except for changing the type of `x` and `y` to `&str` and avoid a useless construction of a `String`, I'm not sure there is much to optimize at compile time here. – mcarton Mar 02 '19 at 14:46
  • So you want a function of your version for each version of `x` ? I doubt that this is what I will call partial application. – Stargateur Mar 02 '19 at 15:56
  • @Stargateur Yes that is correct. sorry if I used the wrong terminology! – sdgfsdh Mar 02 '19 at 16:13
  • 1
    Are you sure this is a performance bottleneck in your code? I would be very surprised if it was. Anyway, if it indeed is, I suggest simply marking `foo()` as `#[inline(always)]` and use normal closures. The compiler would probably inline the function even without the attribute, so there isn't anything to worry about, but if the attribute puts your mind at ease then just go ahead an add it. :) – Sven Marnach Mar 02 '19 at 16:20
  • @SvenMarnach I am asking to learn, not because this is a bottle-neck in a particular piece of code. – sdgfsdh Mar 02 '19 at 16:43
  • 1
    *How can I write this for maximum efficiency* — why do you believe that what you have written **isn't** maximally efficient? – Shepmaster Mar 02 '19 at 18:36
  • 1
    Well I don't know, that's why I am asking. – sdgfsdh Mar 02 '19 at 18:49

2 Answers2

6

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:

E_net4
  • 27,810
  • 13
  • 101
  • 139
2

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()));
}

Playground

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Cerberus
  • 8,879
  • 1
  • 25
  • 40