Allowing an &[impl Trait] to behave like an &[&dyn Trait] - rust

Forgive in advance for the bad title. I will try to be clear in the description.
I am making an application that requires to work with tokio_postresql and tiberius.
I need to provide query parameters for both connectors. This are their signatures.
postgresql
tokio_postgres::client::Client
pub async fn query<T>(&self, statement: &T, params: &[&dyn ToSql + Sync]) -> Result<Vec<Row>, Error>
tiberius
tiberius::query::Query
pub fn bind(&mut self, param: impl IntoSql<'a> + 'a)
As you may observe, tokio_postres admits a reference to an array a trait objets, which is really convenient. But, my bottleneck is with the param of tiberius.
Here's my code:
#[async_trait]
pub trait Transaction<T: Debug> {
/// Performs the necessary to execute a query against the database
async fn query<'a>(stmt: String, params: &'a [&'a (dyn QueryParameters<'a> + Sync)], datasource_name: &'a str)
-> Result<DatabaseResult<T>, Box<(dyn std::error::Error + Sync + Send + 'static)>>
{
let database_connection = if datasource_name == "" {
DatabaseConnection::new(&DEFAULT_DATASOURCE.properties).await
} else { // Get the specified one
DatabaseConnection::new(
&DATASOURCES.iter()
.find( |ds| ds.name == datasource_name)
.expect(&format!("No datasource found with the specified parameter: `{}`", datasource_name))
.properties
).await
};
if let Err(_db_conn) = database_connection {
todo!();
} else {
// No errors
let db_conn = database_connection.ok().unwrap();
match db_conn.database_type {
DatabaseType::PostgreSql => {
let mut m_params: Vec<&(dyn ToSql + Sync)> = Vec::new();
for p in params.iter() {
m_params.push(&p as &(dyn ToSql + Sync))
}
postgres_query_launcher::launch::<T>(db_conn, stmt, params).await
},
DatabaseType::SqlServer =>
sqlserver_query_launcher::launch::<T>(db_conn, stmt, params).await
}
}
}
}
where QueryParameters:
pub trait QueryParameters<'a> {}
impl<'a> QueryParameters<'a> for i32 {}
impl<'a> QueryParameters<'a> for i64 {}
impl<'a> QueryParameters<'a> for &'a str {}
impl<'a> QueryParameters<'a> for String {}
impl<'a> QueryParameters<'a> for &'a String {}
impl<'a> QueryParameters<'a> for &'a [u8] {}
impl<'a> QueryParameters<'a> for &'a (dyn ToSql + Sync + Send) {}
impl<'a> QueryParameters<'a> for &'a dyn IntoSql<'a> {}
1st question:
I want to cast the &'a dyn QueryParameters<'a> to &'a (dyn ToSql + Sync). Is this possible to cast from some trait to another?
2nd question:
The .bind() method of the tiberius client, only accept values that impl IntoSql<'a>.
But I need to mix in my collection different values that already implements IntoSql<'a, but they have different type. I would like to know how to... cast??? those values of type &'a dyn QueryParameters<'a> to the values accepted by the function.
Are those things possible?
NOTE: The launch method from both modules are just a wrapper over the method calls provided above, but they accept as parameter params: &'a[&'a dyn QueryParameters<'a>]
Edit:
pub async fn launch<'a, T>(
db_conn: DatabaseConnection,
stmt: String,
params: &'a [&'a dyn QueryParameters<'a>],
) -> Result<DatabaseResult<T>, Box<(dyn std::error::Error + Send + Sync + 'static)>>
where
T: Debug
{
let mut sql_server_query = Query::new(stmt);
params.into_iter().for_each( |param| sql_server_query.bind( param ));
let client: &mut Client<TcpStream> = &mut db_conn.sqlserver_connection
.expect("Error querying the SqlServer database") // TODO Better msg
.client;
let _results: Vec<Row> = sql_server_query.query(client).await?
.into_results().await?
.into_iter()
.flatten()
.collect::<Vec<_>>();
Ok(DatabaseResult::new(vec![]))
}
that's the more conflictive part for me. .bind(impl IntoSql<'a> + 'a), so I should call this method for every parameter that I want to bind. I would like to cast ' &dyn QueryParameters<'a> to impl ..., but I don't know if that's is even possible.
But, if I change the method signature to:
pub async fn launch<'a, T>(
db_conn: DatabaseConnection,
stmt: String,
params: &'a [impl IntoSql<'a> + 'a],
) -> Result<DatabaseResult<T>, Box<(dyn std::error::Error + Send + Sync + 'static)>>
I just only can accept values of the same type. Imagine a insert query, for example. I need to be flexible to accept both i32, i64, &str... depending on the column type. So this isn't valid for my case.
Edit 2
I've found a way to solve the postgres side of the issue.
trait AsAny {
fn as_any(&self) -> &dyn std::any::Any;
}
impl AsAny for i32 {
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
pub trait QueryParameters<'a> {
fn as_postgres_param(&self) -> &(dyn ToSql + Sync + 'a);
}
impl<'a> QueryParameters<'a> for i32 {
fn as_postgres_param(&self) -> &(dyn ToSql + Sync + 'a) {
let a: Box<&dyn AsAny> = Box::new(self);
match a.as_any().downcast_ref::<i32>() {
Some(b) => b,
None => panic!("Bad conversion of parameters"),
}
}
}
I don't know if it's elegant, or harms performance (sure it does), but I can write now:
let mut m_params: Vec<&(dyn ToSql + Sync)> = Vec::new();
for param in params {
m_params.push(param.as_postgres_param());
}
let query_result = client.query(&stmt, m_params.as_slice()).await;
But I can't figure out still how to work with the impl IntoSql<'a> + 'a of tiberius

Essentially, you need a &dyn QueryParameter to work as both a &dyn ToSql and an impl IntoSql, right? Lets start from scratch:
trait QueryParameter {}
The &dyn ToSql part is easy since you can use the trick shown in this answer. You need your QueryParameter trait to have an associated function to convert from &self to &dyn Sql. Like so:
trait QueryParameter {
fn as_to_sql(&self) -> &dyn ToSql;
The impl IntoSql is trickier since consuming trait objects is a dicey affair. However, to implement the trait, we only need to construct a ColumnData. And we'll see in a second that its just that simple:
trait QueryParameter {
fn as_column_data(&self) -> ColumnData<'_>;
because we can next implement IntoSql for &dyn QueryParameter like I mentioned in your other question:
impl<'a> IntoSql<'a> for &'a dyn QueryParameter {
fn into_sql(self) -> ColumnData<'a> {
self.as_column_data()
}
}
And besides implementation for QueryParameter itself, that's it! We need to sprinkle in some Sync since ToSql and IntoSql require them, but this is a (mostly) working example:
use tiberius::{ColumnData, IntoSql, Query};
use tokio_postgres::types::ToSql;
trait QueryParameter: Sync {
fn as_to_sql(&self) -> &(dyn ToSql + Sync);
fn as_column_data(&self) -> ColumnData<'_>;
}
impl QueryParameter for i32 {
fn as_to_sql(&self) -> &(dyn ToSql + Sync) { self }
fn as_column_data(&self) -> ColumnData<'_> { ColumnData::I32(Some(*self)) }
}
impl QueryParameter for i64 {
fn as_to_sql(&self) -> &(dyn ToSql + Sync) { self }
fn as_column_data(&self) -> ColumnData<'_> { ColumnData::I64(Some(*self)) }
}
impl QueryParameter for &'_ str {
fn as_to_sql(&self) -> &(dyn ToSql + Sync) { self }
fn as_column_data(&self) -> ColumnData<'_> { ColumnData::String(Some((*self).into())) }
}
impl QueryParameter for String {
fn as_to_sql(&self) -> &(dyn ToSql + Sync) { self }
fn as_column_data(&self) -> ColumnData<'_> { ColumnData::String(Some(self.into())) }
}
impl<'a> IntoSql<'a> for &'a dyn QueryParameter {
fn into_sql(self) -> ColumnData<'a> {
self.as_column_data()
}
}
async fn via_tiberius(stmt: &str, params: &[&dyn QueryParameter]) {
let mut client: tiberius::Client<_> = todo!();
let mut query = Query::new(stmt);
for &param in params {
query.bind(param)
}
let _ = query.execute(&mut client).await;
}
async fn via_tokio_postgres(stmt: &str, params: &[&dyn QueryParameter]) {
let client: tokio_postgres::Client = todo!();
let params: Vec<_> = params.iter().map(|p| p.as_to_sql()).collect();
let _ = client.query(stmt, &params).await;
}

Related

Lifetimes and ownership with traits

I am building a Rocket app and want it to manage some objects for me. For that, they need to be Send and Sync. Here's a minimal contrived example that shows the error I am getting (Playground):
trait MyTrait {
fn foo(&self) {}
}
struct TraitThing {}
impl MyTrait for TraitThing {
fn foo(&self) {
println!("Foo!");
}
}
struct Thing {
tt: &'static (dyn MyTrait + Send + Sync),
}
impl Thing {
fn with_trait(tt: &'static (dyn MyTrait + Send + Sync)) -> Self {
Self { tt }
}
}
fn main() {
let tt = TraitThing {};
let thing = Thing::with_trait(&tt);
thing.tt.foo();
}
I probably don't understand lifetimes of 'static. Ideally, I want Thing to own tt, but as far as I understand, since my TraitThing isn't Sized (and probably won't ever be), it cannot be a trait object, so it must be passed by reference.
So, how do I solve this while keeping the Send and Sync traits?
Thanks!
Additional note: I've been reading a lot about lifetimes and traits in the Rust book and other places, but I'd appreciate further reading material.
You need to use Box:
trait MyTrait {
fn foo(&self) {}
}
struct TraitThing {}
impl MyTrait for TraitThing {
fn foo(&self) {
println!("Foo!");
}
}
struct Thing {
tt: Box<dyn MyTrait + Send + Sync>,
}
impl Thing {
fn with_trait(tt: Box<dyn MyTrait + Send + Sync>) -> Self {
Self { tt }
}
}
fn main() {
let tt = TraitThing {};
let thing = Thing::with_trait(Box::new(tt));
thing.tt.foo();
}
Playground
You can use a Box but it will allocate to the Heap and use dynamic dispatch which is not useful in your case. It is useful when you want a list of different type that implements a similar Trait like :
struct Thing {
tt: Vec<Box<dyn MyTrait + Send + Sync>>,
}
But in your case you only have one element so you can use Generics in this setup:
trait MyTrait {
fn foo(&self) {}
}
struct TraitThing {}
impl MyTrait for TraitThing {
fn foo(&self) {
println!("Foo!");
}
}
struct Thing<T>
where
T: MyTrait + Send + Sync
{
tt: T,
}
impl<T> Thing<T>
where
T: MyTrait + Send + Sync
{
fn with_trait(tt: T) -> Self {
Self { tt }
}
}
fn main() {
let tt = TraitThing {};
let thing = Thing::with_trait(tt);
thing.tt.foo();
}

How to use dynamic dispatch with a method which takes an iterator as a parameter?

I am writing a command line application in rust for processing audio from a sensor. I would like the user to be able to choose an algorithm or filter to apply from several options. I was hoping to use dynamic dispatch to switch out a struct which implements my filter trait at runtime. However, this is not allowed by the compiler, because one of the trait methods takes a generic parameter.
How could I implement this same functionality, without causing any compiler troubles? I know that an easy solution is to change the parameter of the process method to an array or a vector, but this is my last resort, as I would much prefer to take an iterator or an IntoIterator, as it is more general, and suits my specific needs.
Here is some code which demonstrates the problem.
trait SensorFilter {
fn process(&self, sig: &mut impl Iterator<Item = f32>) -> Vec<f32>;
}
struct Alg1 {
mul: f32,
}
struct Alg2 {
add: f32,
}
impl SensorFilter for Alg1 {
fn process(&self, sig: &mut impl Iterator<Item = f32>) -> Vec<f32> {
sig.map(|x| x * self.mul).collect()
}
}
impl SensorFilter for Alg2 {
fn process(&self, sig: &mut impl Iterator<Item = f32>) -> Vec<f32> {
sig.map(|x| x * self.add).collect()
}
}
enum AlgChoice {
Alg1,
Alg2
}
fn main() {
let choice = AlgChoice::Alg1; // user chooses via command-line.
let mut sig = vec![0.,1.,2.,3.,4.,5.,6.].into_iter(); // iterator gets data from sensor.
// This doesn't work, because my trait cannot be made into an object.
let alg: &dyn SensorFilter = match choice {
AlgChoice::Alg1 => Alg1{mul:0.3},
_ => Alg2{add:1.2},
};
let result = alg.process(&mut sig);
println!("{:?}",result);
}
Thanks :)
The trick here is to change your generic function parameter to a generic trait parameter:
// Make the generic param into a type argument w/ constraints
trait SensorFilter<I> where I: Iterator<Item = f32> {
fn process(&self, sig: &mut I) -> Vec<f32>;
}
struct Alg1 {
mul: f32,
}
struct Alg2 {
add: f32,
}
// Implement trait for all I that match the iterator constraint
impl<I: Iterator<Item = f32>> SensorFilter<I> for Alg1 {
fn process(&self, sig: &mut I) -> Vec<f32> {
sig.map(|x| x * self.mul).collect()
}
}
impl<I: Iterator<Item = f32>> SensorFilter<I> for Alg2 {
fn process(&self, sig: &mut I) -> Vec<f32> {
sig.map(|x| x * self.add).collect()
}
}
enum AlgChoice {
Alg1,
Alg2
}
fn main() {
let choice = AlgChoice::Alg1; // user chooses via command-line.
let mut sig = vec![0.,1.,2.,3.,4.,5.,6.].into_iter(); // iterator gets data from sensor.
// Specify the type argument of your trait.
let alg: &dyn SensorFilter<std::vec::IntoIter<f32>> = match choice {
AlgChoice::Alg1 => &Alg1{mul:0.3},
_ => &Alg2{add:1.2},
};
let result = alg.process(&mut sig);
println!("{:?}",result);
}
The simplest way to make SensorFilter object safe is to simply change process to accept dyn Iterator instead of impl Iterator:
trait SensorFilter {
fn process(&self, sig: &mut dyn Iterator<Item = f32>) -> Vec<f32>;
}
If you couldn't do this, for example because Iterator were actually non-object-safe, you could instead extract the common, non-object-safe part into a second trait, and implement it automatically for everything that is SensorFilter:
// This trait is object-safe.
trait SensorFilter {
fn filter(&self, x: f32) -> f32;
}
// This trait will not be object-safe because it uses generics.
trait Process {
fn process<I: IntoIterator<Item = f32>>(&self, sig: I) -> Vec<f32>;
}
// The `?Sized` bound allows you to call `.process()` on `dyn SensorFilter`.
impl<T: ?Sized + SensorFilter> Process for T {
fn process<I: IntoIterator<Item = f32>>(&self, sig: I) -> Vec<f32> {
sig.into_iter().map(|x| self.filter(x)).collect()
}
}
// ...
impl SensorFilter for Alg1 {
fn filter(&self, x: f32) -> f32 {
x * self.mul
}
}
impl SensorFilter for Alg2 {
fn filter(&self, x: f32) -> f32 {
x * self.add
}
}
Playground
Note that instead of Iterator I used IntoIterator, which is strictly more general.
A variation on this idea, when you can't easily remove the genericity from SensorFilter, is to use double dispatch: write SensorFilter to use dyn Iterator instead of impl Iterator, and then write a convenience trait that just wraps it with the specific type:
trait SensorFilter {
fn process_dyn(&self, sig: &mut dyn Iterator<Item = f32>) -> Vec<f32>;
}
trait Process {
fn process<I: IntoIterator<Item = f32>>(&self, sig: I) -> Vec<f32>;
}
impl<T: ?Sized + SensorFilter> Process for T {
fn process<I: IntoIterator<Item = f32>>(&self, sig: I) -> Vec<f32> {
self.process_dyn(&mut sig.into_iter())
}
}

lifetime with closure captures in rust

How can I reduce the lifetime of a closure?
I was trying to make a method, which returns an iterator related to self. I didn't want to make new struct or something, so I just made it return filters and maps, and confronted some borrow checker errors.
The following code was my first try.
fn f<'b>(&'b self) -> impl Iterator<Item = u8> {
(0..self.some_number())
.filter(|&i| self.some_bool_function(i))
.map(|i| i as u8)
}
The following code replicates my question.
struct A(bool);
impl A {
fn f<'a>(&'a self) -> impl Iterator<Item = u8> + 'a {
(0..1).filter(|&i| self.0)
}
}
or even shorter,
fn g<'a>(t:&'a ()) -> impl 'a + FnMut() {
|| *t
}
This would not compile, because the closure may outlive self. I don't know how to make this work, without moving self.
If you return a closure, you must ensure that the closure has everything it needs - even after returning (i.e. after the (temporary) function parameters are popped from the stack).
Thus, I think you want to move the stuff you return into the closure:
impl A {
fn f<'a>(&'a self) -> impl Iterator<Item = u8> + 'a {
(0..1).filter(move |&i| self.0)
}
}
Resp.
fn g<'a>(t:&'a ()) -> impl 'a + FnMut() {
move || *t
}
Resp (extending your first example):
struct A(bool);
impl A {
fn some_number(&self) -> usize {
6
}
fn some_bool_function(&self, i: usize) -> bool {
i%2==0
}
fn f<'b>(&'b self) -> impl Iterator<Item = u8> + 'b {
(0..self.some_number())
.filter(move |&i| self.some_bool_function(i))
.map(|i| i as u8)
}
}

How to accept `Box<dyn Error + Send>` in places that accept `Box<dyn Error>`?

Can I accept a Box<dyn Error + Send> in places that accept Box<dyn Error>? If yes, how? If no, why, and is there a more elegant way to do it than in the following example?
#![allow(unused)]
use std::error::Error as StdError;
use std::result::Result as StdResult;
type Result = StdResult<(), Box< dyn StdError >>;
type SndResult = StdResult<(), Box< dyn StdError + Send >>;
fn fn_returning_result() -> Result { Ok(()) }
fn fn_returning_sndresult() -> SndResult { Ok(()) }
/// Accept a callback that returns a non-Send `Result`.
fn register_callback<CB: FnOnce() -> Result>(cb: CB) { /* ... */ }
fn main() -> Result {
// Is there a way to get rid of ... vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv ... this part?
let cb = || fn_returning_sndresult().map_err(|e| -> Box<dyn StdError> { e });
register_callback(cb);
Ok(())
}
Playground
Edit: What I would like to do here is just let cb = || fn_returning_sndresult();, but that won't compile.
Also, I'd like to call either fn_returning_result()? or fn_returning_sndresult()? inside the callback with a single return type and without doing a map_err.
This is based on an answer I got on Reddit by Llaurence_:
I can change register_callback to accept both Box<dyn Error> and Box<dyn Error + Send> errors by making the error type generic:
fn register_callback<CB, E>(cb: CB)
where
CB: FnOnce() -> StdResult<(), Box<E>>,
E: StdError + ?Sized,
{ /* ... */ }
Now I can pass a callback that returns either kind of error / result:
register_callback(fn_returning_result);
register_callback(fn_returning_sndresult);
Q: Can I accept a Box<dyn Error + Send> in places that accept Box<dyn Error>?
A: Yes, with some newtype wrapping. The solution I finally ended up with was this: (Playground)
use std::error::Error as StdError;
use std::result::Result as StdResult;
use std::fmt;
type Result = StdResult<(), Box< dyn StdError >>;
type SndResult = StdResult<(), Box< dyn StdError + Send >>;
fn fn_returning_result() -> Result { dbg!(Err("oops".into())) }
fn fn_returning_sndresult() -> SndResult { dbg!(Ok(())) }
/// Result type using `Er` defined below.
type Rt<T = ()> = StdResult<T, Er>;
// Error wrapper
#[derive(Debug)]
struct Er(Box<dyn StdError>);
impl fmt::Display for Er {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self, f)
}
}
impl StdError for Er {}
// Allow `?` operator
impl From<Box<dyn StdError>> for Er {
fn from(err: Box<dyn StdError>) -> Self {
Er(err)
}
}
impl From<Box<dyn StdError + Send>> for Er {
fn from(err: Box<dyn StdError + Send>) -> Self {
Er(err)
}
}
// List of callbacks
struct Callbacks<'a>(Vec<Box<dyn FnOnce() -> Rt + 'a>>);
impl<'a> Callbacks<'a> {
fn new() -> Self {
Callbacks(Vec::new())
}
fn add(&mut self, cb: impl FnOnce() -> Rt + 'a) {
self.0.push(Box::new(cb));
}
fn pop_and_call(&mut self) -> Rt {
self.0.pop().unwrap()()
}
}
// Example
fn main() -> Result {
let mut callbacks = Callbacks::new();
callbacks.add(|| { Ok(fn_returning_result()?) });
callbacks.add(|| { Ok(fn_returning_sndresult()?) });
callbacks.pop_and_call()?;
callbacks.pop_and_call()?;
Ok(())
}
Note that the callbacks can use ? on either Result or SndResult values (thanks to the From trait impls), and that their return values are valid Results as well, allowing main to also use ? on their calls (thanks to the StdError trait impl).

How can I explicitly specify a lifetime when implementing a trait?

Given the implementation below, where essentially I have some collection of items that can be looked up via either a i32 id field or a string field. To be able to use either interchangeably, a trait "IntoKey" is used, and a match dispatches to the appropriate lookup map; this all works fine for my definition of get within the MapCollection impl:
use std::collections::HashMap;
use std::ops::Index;
enum Key<'a> {
I32Key(&'a i32),
StringKey(&'a String),
}
trait IntoKey<'a> {
fn into_key(&'a self) -> Key<'a>;
}
impl<'a> IntoKey<'a> for i32 {
fn into_key(&'a self) -> Key<'a> { Key::I32Key(self) }
}
impl<'a> IntoKey<'a> for String {
fn into_key(&'a self) -> Key<'a> { Key::StringKey(self) }
}
#[derive(Debug)]
struct Bar {
i: i32,
n: String,
}
struct MapCollection
{
items: Vec<Bar>,
id_map: HashMap<i32, usize>,
name_map: HashMap<String, usize>,
}
impl MapCollection {
fn new(items: Vec<Bar>) -> MapCollection {
let mut is = HashMap::new();
let mut ns = HashMap::new();
for (idx, item) in items.iter().enumerate() {
is.insert(item.i, idx);
ns.insert(item.n.clone(), idx);
}
MapCollection {
items: items,
id_map: is,
name_map: ns,
}
}
fn get<'a, K>(&self, key: &'a K) -> Option<&Bar>
where K: IntoKey<'a> //'
{
match key.into_key() {
Key::I32Key(i) => self.id_map.get(i).and_then(|idx| self.items.get(*idx)),
Key::StringKey(s) => self.name_map.get(s).and_then(|idx| self.items.get(*idx)),
}
}
}
fn main() {
let bars = vec![Bar { i:1, n:"foo".to_string() }, Bar { i:2, n:"far".to_string() }];
let map = MapCollection::new(bars);
if let Some(bar) = map.get(&1) {
println!("{:?}", bar);
}
if map.get(&3).is_none() {
println!("no item numbered 3");
}
if let Some(bar) = map.get(&"far".to_string()) {
println!("{:?}", bar);
}
if map.get(&"baz".to_string()).is_none() {
println!("no item named baz");
}
}
However, if I then want to implement std::ops::Index for this struct, if I attempt to do the below:
impl<'a, K> Index<K> for MapCollection
where K: IntoKey<'a> {
type Output = Bar;
fn index<'b>(&'b self, k: &K) -> &'b Bar {
self.get(k).expect("no element")
}
}
I hit a compiler error:
src/main.rs:70:18: 70:19 error: cannot infer an appropriate lifetime for automatic coercion due to conflicting requirements
src/main.rs:70 self.get(k).expect("no element")
^
src/main.rs:69:5: 71:6 help: consider using an explicit lifetime parameter as shown: fn index<'b>(&'b self, k: &'a K) -> &'b Bar
src/main.rs:69 fn index<'b>(&'b self, k: &K) -> &'b Bar {
src/main.rs:70 self.get(k).expect("no element")
src/main.rs:71 }
I can find no way to specify a distinct lifetime here; following the compiler's recommendation is not permitted as it changes the function signature and no longer matches the trait, and anything else I try fails to satisfy the lifetime specification.
I understand that I can implement the trait for each case (i32, String) separately instead of trying to implement it once for IntoKey, but I am more generally trying to understand lifetimes and appropriate usage. Essentially:
Is there actually an issue the compiler is preventing? Is there something unsound about this approach?
Am I specifying my lifetimes incorrectly? To me, the lifetime 'a in Key/IntoKey is dictating that the reference need only live long enough to do the lookup; the lifetime 'b associated with the index fn is stating that the reference resulting from the lookup will live as long as the containing MapCollection.
Or am I simply not utilizing the correct syntax to specify the needed information?
(using rustc 1.0.0-nightly (b63cee4a1 2015-02-14 17:01:11 +0000))
Do you intend on implementing IntoKey on struct's that are going to store references of lifetime 'a? If not, you can change your trait and its implementations to:
trait IntoKey {
fn into_key<'a>(&'a self) -> Key<'a>;
}
This is the generally recommended definition style, if you can use it. If you can't...
Let's look at this smaller reproduction:
use std::collections::HashMap;
use std::ops::Index;
struct Key<'a>(&'a u8);
trait IntoKey<'a> { //'
fn into_key(&'a self) -> Key<'a>;
}
struct MapCollection;
impl MapCollection {
fn get<'a, K>(&self, key: &'a K) -> &u8
where K: IntoKey<'a> //'
{
unimplemented!()
}
}
impl<'a, K> Index<K> for MapCollection //'
where K: IntoKey<'a> //'
{
type Output = u8;
fn index<'b>(&'b self, k: &K) -> &'b u8 { //'
self.get(k)
}
}
fn main() {
}
The problem lies in get:
fn get<'a, K>(&self, key: &'a K) -> &u8
where K: IntoKey<'a>
Here, we are taking a reference to K that must live as long as the Key we get out of it. However, the Index trait doesn't guarantee that:
fn index<'b>(&'b self, k: &K) -> &'b u8
You can fix this by simply giving a fresh lifetime to key:
fn get<'a, 'b, K>(&self, key: &'b K) -> &u8
where K: IntoKey<'a>
Or more succinctly:
fn get<'a, K>(&self, key: &K) -> &u8
where K: IntoKey<'a>

Resources