如何在没有堆分配的情况下转换切片引用的元素?

How do I cast the elements referred by a slice without heap allocation?

假设有一个参数数组需要在 SQL 查询中使用。每个参数必须是 &dyn ToSql,已为 &str.

实现

需要将对象同时用作 &dyn ToSql&str,如下例所示,需要实现 Display 才能打印出来.

let params = ["a", "b"];

// this works but allocates
// let tx_params = &params
//             .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);

错误:

   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

特性ToSql没有为str实现,但它是为&str实现的,但是我们借用checked不会让我们在这里借用p,即使我们没有对数据做任何事情,只是将其转换为新类型。

Playground

一个月前我打过这个,我的一般建议是:不要打扰。实际查询比分配重得多。

情况有点混乱,因为你需要一个&ToSql,但是ToSql is implemented&str,所以你需要两个数组:一个[&str],还有一个 [&ToSql],其元素引用 &strs - 所以 [&ToSql] 的内容是双重引用。如果不分配,我看不到实现这一目标的简单方法。 (let params: [&&str; 2] = params.iter().collect::<Vec<_>>().try_into().unwrap(); 有效,分配可能会被优化。存在夜间或不安全的方式,请参阅@ChayimFriedman 的回答。)

在这种情况下,您可以通过最初声明来解决:

let params = [&"a", &"b"];

通过使用迭代器,而不是数组:

let iter = params.iter().map(|p| p as &(dyn ToSql + Sync));
client.query_raw("select * from foo where id in ?", iter);

在我的例子中,我不能做这样的事情,因为我使用的是执行,而不是查询,并且 execute_raw 只存在于 tokio-postgres,而不存在于 postgres ].所以要小心这些陷阱。

我同意@Caesar 对此的看法,但是您实际上可以在没有堆分配的情况下做到这一点。

您可以使用 <[T; N]>::each_ref()(此方法将 &[T; N] 转换为 [&T; N]):

params.each_ref().map(|p| p as &(dyn ToSql + Sync));

Playground.

不幸的是each_ref() is unstable,但您可以使用带有不安全代码的稳定 Rust 来编写它:

use std::iter;
use std::mem::{self, MaybeUninit};

fn array_each_ref<T, const N: usize>(arr: &[T; N]) -> [&T; N] {
    let mut result = [MaybeUninit::uninit(); N];
    for (result_item, arr_item) in iter::zip(&mut result, arr) {
        *result_item = MaybeUninit::new(arr_item);
    }
    // SAFETY: `MaybeUninit<T>` is guaranteed to have the same layout as `T`; we
    // initialized all items above (can be replaced with `MaybeUninit::array_assume_init()`
    // once stabilized).
    unsafe { mem::transmute_copy(&result) }
}

Playground.