Reputation: 3118
Let's suppose there's an array of parameters that need to be used in SQL query. Each parameter must be a &dyn ToSql
,which is implemented already for &str
.
The need arises to use the object both as &dyn ToSql
and as &str
, like in the example down below, where it needs to implement Display
in order to be printed out.
let params = ["a", "b"];
// this works but allocates
// let tx_params = ¶ms
// .iter()
// .map(|p| p as &(dyn ToSql + Sync))
// .collect::<Vec<_>>();
// this is ideal, doesn't allocate on the heap, but doesn't work
params.map(|p| p as &(dyn ToSql + Sync));
// this has to compile, so can't just crate `params` as [&(dyn ToSql + Sync)] initially
println!("Could not insert {}", params);
Error:
Compiling playground v0.0.1 (/playground)
error[E0277]: the trait bound `str: ToSql` is not satisfied
--> src/main.rs:14:20
|
14 | params.map(|p| p as &(dyn ToSql + Sync));
| ^ the trait `ToSql` is not implemented for `str`
|
= help: the following implementations were found:
<&'a str as ToSql>
= note: required for the cast to the object type `dyn ToSql + Sync`
error[E0277]: the size for values of type `str` cannot be known at compilation time
--> src/main.rs:14:20
|
14 | params.map(|p| p as &(dyn ToSql + Sync));
| ^ doesn't have a size known at compile-time
|
= help: the trait `Sized` is not implemented for `str`
= note: required for the cast to the object type `dyn ToSql + Sync`
For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` due to 2 previous errors
The trait ToSql
isn't implemented for str
, but it is for &str
, however we borrow checked won't let us borrow p
here, even though we're not doing anything with the data, except cast it as a new type.
Upvotes: 1
Views: 509
Reputation: 71555
I agree with @Caesar's take on this, however you actually can do that without heap allocations.
You can use <[T; N]>::each_ref()
for that (this method converts &[T; N]
to [&T; N]
):
params.each_ref().map(|p| p as &(dyn ToSql + Sync));
Upvotes: 1
Reputation: 8544
I fought this a month ago and my general recommendation is: Don't bother. The actual query is so much heavier than an allocation.
The situation is a bit confusing, because you need an &ToSql
, but ToSql
is implemented for &str
, so you need two arrays: One [&str]
, and one [&ToSql]
, whose elements reference &str
s - so the contenst of [&ToSql]
are double references. I don't see an easy way of achieving that without allocating. (let params: [&&str; 2] = params.iter().collect::<Vec<_>>().try_into().unwrap();
works and the allocation will likely be optimized out. Nighly or unsafe ways exist, see @ChayimFriedman's answer.)
In this case, you can work around either by initially declaring:
let params = [&"a", &"b"];
by using an iterator, not an array:
let iter = params.iter().map(|p| p as &(dyn ToSql + Sync));
client.query_raw("select * from foo where id in ?", iter);
In my case, I wasn't able to do anything like this because I was using execute, not query, and execute_raw
exists only on tokio-postgres
, but not on postgres
. So beware of these kinds of pitfalls.
Upvotes: 1