Background
I'm trying to write a helper function for constructing repeating strings with a separator.
// "hello world hello world hello"
"hello".repeat_and_join(3, " world ");
// "?,?,?"
'?'.repeat_and_join(3, ',');
The intended use case is to construct placeholders in sqlx query strings.
let ids = vec![52, 36, 29];
query_as::<_, MyEntity>(
format!(
r#"SELECT * FROM my_entities WHERE id IN ({})"#,
'?'.repeat_and_join(ids.len(), ',')
)
.as_str()
)
Isn't this already supported by sqlx? Unfortunately, not at the time of writing.
Code
I wanted to create a generic method similar to join, supporting String, &str and char. Unfortunately, rust does not support method overloading - and I don't fully understand the syntax used to implement join. My attempt therefore makes use of a public method with an enum parameter and private implementation functions. However, I think this looks a bit ugly on the caller side (having to create the sep argument using e.g. StringOrChar::Char(',')) and causes a lot of repetition on the implementation side.
Is there a way to (i) make the caller API not have to use StringOrChar::X() and (ii) reduce the repeated code?
pub(crate) enum StringOrChar<'a> {
String(String),
Str(&'a str),
Char(char),
}
pub(crate) trait StringExt {
fn repeat_and_join(&self, n: usize, sep: StringOrChar) -> String;
}
impl StringExt for String {
fn repeat_and_join(&self, n: usize, sep: StringOrChar) -> String {
match sep {
StringOrChar::String(sep) => repeat_and_join_str_str(n, self.as_str(), sep.as_str()),
StringOrChar::Str(sep) => repeat_and_join_str_str(n, self.as_str(), sep),
StringOrChar::Char(sep) => repeat_and_join_str_char(n, self.as_str(), sep),
}
}
}
impl StringExt for &str {
fn repeat_and_join(&self, n: usize, sep: StringOrChar) -> String {
match sep {
StringOrChar::String(sep) => repeat_and_join_str_str(n, self, sep.as_str()),
StringOrChar::Str(sep) => repeat_and_join_str_str(n, self, sep),
StringOrChar::Char(sep) => repeat_and_join_str_char(n, self, sep),
}
}
}
impl StringExt for char {
fn repeat_and_join(&self, n: usize, sep: StringOrChar) -> String {
match sep {
StringOrChar::String(sep) => repeat_and_join_char_str(n, *self, sep.as_str()),
StringOrChar::Str(sep) => repeat_and_join_char_str(n, *self, sep),
StringOrChar::Char(sep) => repeat_and_join_char_char(n, *self, sep),
}
}
}
fn repeat_and_join_str_str(n: usize, rep: &str, sep: &str) -> String {
let mut s = String::with_capacity(rep.len() * n + sep.len() * (n.max(1) - 1));
for _ in 0..(n.max(1) - 1) {
s.push_str(rep);
s.push_str(sep);
}
if n > 0 {
s.push_str(rep);
}
s
}
fn repeat_and_join_str_char(n: usize, rep: &str, sep: char) -> String {
let mut s = String::with_capacity(rep.len() * n + (n.max(1) - 1));
for _ in 0..(n.max(1) - 1) {
s.push_str(rep);
s.push(sep);
}
if n > 0 {
s.push_str(rep);
}
s
}
fn repeat_and_join_char_str(n: usize, rep: char, sep: &str) -> String {
let mut s = String::with_capacity(n + sep.len() * (n.max(1) - 1));
for _ in 0..(n.max(1) - 1) {
s.push(rep);
s.push_str(sep);
}
if n > 0 {
s.push(rep);
}
s
}
fn repeat_and_join_char_char(n: usize, rep: char, sep: char) -> String {
let mut s = String::with_capacity(n + (n.max(1) - 1));
for _ in 0..(n.max(1) - 1) {
s.push(rep);
s.push(sep);
}
if n > 0 {
s.push(rep);
}
s
}