no, "" not special, it's just have a different type. any string literal will have the type &'static str, while b.to_string().as_str() first create an owned String than borrows it, but the created String is a temporary variable, the borrows can't satisfy the lifetime of the return type.
it is not possible without leaking memory, because the paramter of from() is B which doesn't contain any allocated memory for the string representation, but your return type A<'a> need to borrow the underlying str, which only exists when the temporary String value returned by to_string() is in scope. but the temporary String is dropped as soon as you return from from().
A generic lifetime parameter is that – a parameter, ie. the caller chooses it.
In from_empty<'l>(), this works because whatever 'l is, the string literal's 'static lifetime will satisfy it (&'static str can be shortened to &'l str for any 'l), but it's kind of misleading, because it masks your misunderstanding.
from<'l>() doesn't work because there the lifetime is local to the function, and so it can't be arbitrarily chosen by the caller – it is determined by the function body, and there's no way to extend it. (In particular, the lifetime of the str you are creating is the lifetime of a temporary inside the function, because that's what .to_string() returns.)
No, that doesn't make any sense. You would be returning a dangling reference to a temporary that was already destroyed.
In Rust, references don't keep alive values; you don't use references to "return by reference". If you want to return a string created inside a function, then return a String by-value.
To elaborate a bit more, the common point between Rust references and those in garbage-collected other programming languages is that, as long as you have a reference, you know that the reference's target is valid and accessible. The mechanism that ensures this is very different between the two, though:
In a garbage-collected language, holding on to a reference will delay the destruction of the target until the reference is gone.
In Rust, destroying or moving a value will invalidate any references to it¹. Or, equivalently, holding a reference will forbid moving or destroying its target. Whichever way you look at it, this is checked at compile-time: Any use of an invalid reference (or move of a referenced value) will prevent the program from compiling.
There are lots of tradeoffs between these two approaches, but the most relevant one here is that Rust allows you to control where and when every value exists quite precisely. It also requires you to do this, which may feel overly pedantic if you're used to GC'd languages.
¹ The actual rules and mechanism are more complicated and nuanced, but this is a good mental model.
I think I will redesign the type, since I only care some of the time if i'm using ´&str´ or ´String´. i.e. I want ´&str´ in one module and ´String´ in another. So I think I can use:
use std::fmt::Display;
#[derive(Debug, Copy, Clone)]
enum B {
J,
}
impl Display for B {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}",self )
}
}
struct Example<T: Display> {
s: T,
b: B
}
fn a_from_b(b: B) -> Example<String> {
Example {
b,
s: b.to_string()
}
}
fn empty_a<'a>(b: B) -> Example<&'a str> {
Example {
b, s: ""
}
}
fn a_from_b_str(b: B, s: &str) -> Example<&str> {
Example {
b, s
}
}
fn main() {
let a1 = a_from_b(B::J);
let a2 = empty_a(B::J);
let a3 = a_from_b_str(B::J, B::J.to_string().as_str());
}