Rust: conversion from generic with trait bound affects trait object type

I'm working on a simple opengl wrapper library in rust.

Im using nalgebra_glm crate for the math operations. Lots of types implement AsRef for the access to the underlying array. I manually implemented Uniform for array types that match common matrix sizes like [[T; 4]; 4], [T; 16], [T; 3] and so on.

So I can obtain a new Box<dyn Uniform> by calling Box::new(<nalgebra_glm matrix or vector>.as_ref().clone()) but it's unnecessarily verbose.

I wanted to create a convenience function that converts any &[T] which is Clone and AsRef to some type U that implements Uniform into Vec<Box<dyn Uniform>>. Something similar to ToOwned trait.

Here's what I came up with.

pub trait Uniform {
    fn bind(&self, location: GLint);
}
pub fn to_owned<U: Uniform + Clone, T: AsRef<U>>(uniforms: &[T]) -> Vec<Box<dyn Uniform>>
where Vec<Box<dyn Uniform>>: FromIterator<Box<U>>
{
    uniforms.into_iter()
        .map(AsRef::as_ref)
        .map(Clone::clone)
        .map(Box::new)
        .collect()
}

But then when I tried using this function in the following context it caused and error which I'm struggling to understand.

perspective_matrix() and view_matrix() are both of type Mat4 and provide a AsRef<[[f32; 4]; 4].

let common_uniforms = to_owned(&[camera.perspective_matrix(), camera.view_matrix()]);
error[E0277]: the trait bound `(dyn Uniform + 'static): Clone` is not satisfied
   --> src\main.rs:167:27
    |
167 |     let common_uniforms = to_owned(&[camera.perspective_matrix(), camera.view_matrix()]);
    |                           ^^^^^^^^ the trait `Clone` is not implemented for `(dyn Uniform + 'static)`
    |
note: required by a bound in `uniform::to_owned`
   --> src\uniform.rs:9:30
    |
9   | pub fn to_owned<U: Uniform + Clone, T: AsRef<U>>(uniforms: &[T]) -> Vec<Box<dyn Uniform>>
    |                              ^^^^^ required by this bound in `uniform::to_owned`

Why is Clone required by the resulting trait object? clone is only needed during operations on generic U and thus only U should implement Clone. Why does it have anything to do with the final trait object? I would expect that since U implements Uniform it should be possible to create a dyn Uniform trait object out of it.

Also I cannot require Clone as super trait for Uniform since it would make it not object safe.

I have tried explicitly casting resulting box type into trait object, adding 'static lifetime bound but to no avail.

pub fn to_owned<U: 'static + Uniform + Clone, T: AsRef<U>>(uniforms: &[T]) -> Vec<Box<dyn Uniform>>
where Vec<Box<dyn Uniform>>: FromIterator<Box<U>>
{
    uniforms.into_iter()
        .map(AsRef::as_ref)
        .map(Clone::clone)
        .map(|uniform| Box::new(uniform) as Box<dyn Uniform>)
        .collect()
}

I really don't understand what's wrong with my code. It's either that I'm doing some syntactic mistake or there's deeper logical error with what I'm trying to accomplish here.

I would greatly appreciate any help.

Upvotes: 1

Views: 229

Answers (1)

Finomnis
Finomnis

Reputation: 22728

The main problem is your to_owned function.

With where Vec<Box<dyn Uniform>>: FromIterator<Box<U>> you are actually hiding the real problem, causing very confusing compiler messages.

You probably added this because the compiler suggested it. But the compiler also warns that while this is what it needs, adding the where clause is probably not the correct solution. With the where clause, instead of specifying correct bounds that make the conversion possible, you just move the problem out of the function. I agree with the compiler that this is the wrong solution.

Your second attempt is almost correct, all you are missing is that you need to remove the where clause:

use nalgebra_glm::Mat4;

fn perspective_matrix() -> Mat4 {
    todo!()
}

pub trait Uniform {
    fn bind(&self, location: i32);
}

impl Uniform for [[f32; 4]; 4] {
    fn bind(&self, _location: i32) {
        todo!()
    }
}

pub fn to_owned<T, U>(uniforms: &[T]) -> Vec<Box<dyn Uniform>>
where
    T: AsRef<U>,
    U: Uniform + Clone + 'static,
{
    uniforms
        .into_iter()
        .map(AsRef::as_ref)
        .map(Clone::clone)
        .map(|uniform| Box::new(uniform) as Box<dyn Uniform>)
        .collect()
}

fn main() {
    let _common_uniforms = to_owned(&[perspective_matrix()]);
}

Also, this is how a minimal reproducible example looks like ;)

Upvotes: 0

Related Questions