This trait cannot be made into an object

So I have a language model that can either be OpenAI/Ollama or a customer user implemented one. I want to create a summarize_text function that takes this model dynamically during runtime but hitting with compile time issues primarily around this trait cannot be made into an object..

Inline code:

use async_trait::async_trait;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum AIError {
    #[error("Rate limit exceeded: retry after {0} seconds")]
    RateLimit(u64),

    #[error("Model not found: {0}")]
    ModelNotFound(String),

    #[error("Service unavailable: {0}")]
    ServiceUnavailable(String),

    #[error("Unknown error: {0}")]
    Unknown(String),
}

#[derive(Clone, Debug)]
pub struct GenerateTextRequest<M: LanguageModel> {
    model: M,
    prompt: String,
}

#[async_trait]
pub trait LanguageModel: Clone + Send + Sync {
    async fn generate_text(&self, request: &GenerateTextRequest<Self>) -> Result<String, AIError>
    where
        Self: Sized;
}

#[derive(Clone, Debug)]
struct OpenAILanguageModel {}

#[async_trait]
impl LanguageModel for OpenAILanguageModel {
    async fn generate_text(&self, _request: &GenerateTextRequest<Self>) -> Result<String, AIError> {
        Ok("openai".to_string())
    }
}

#[derive(Clone, Debug)]
struct OllamaLanguageModel {}

#[async_trait]
impl LanguageModel for OllamaLanguageModel {
    async fn generate_text(&self, _request: &GenerateTextRequest<Self>) -> Result<String, AIError> {
        Ok("ollama".to_string())
    }
}

pub async fn generate_text<M, R>(request: R) -> Result<String, AIError>
where
    M: LanguageModel,
    R: Into<GenerateTextRequest<M>>,
{
    let request = request.into();
    // TODO: Implement retry logic here
    request.model.generate_text(&request).await
}


pub async fn summarize<M, R>(model: &M, text: &str) -> Result<String, AIError>
where
    M: LanguageModel,
    R: Into<GenerateTextRequest<M>>,
{
    let request = GenerateTextRequest {
        model: model.clone(),
        prompt: format!("Summarize the following text:\n\n{}", text.to_string()),
    };
    generate_text(request).await
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // if openai key defined in env use open ai model else use ollama model
    let openai_key = std::env::var("OPENAI_KEY").ok();
    
    let model = if openai_key.is_some() {
        Box::new(OpenAILanguageModel {}) as Box<dyn LanguageModel>
    } else {
        Box::new(OllamaLanguageModel {}) as Box<dyn LanguageModel>
    };

    summarize(&model, "This is a test").await?;
    
    Ok(())
}

What is a good way to enable dynamic runtime selection of LanguageModel for summarize here. Enum of OpenAI and Ollama won't work here since in the future others crates might provide more Models and we can't know the list all up front.

error[E0038]: the trait `LanguageModel` cannot be made into an object
  --> src/main.rs:81:45
   |
81 |         Box::new(OpenAILanguageModel {}) as Box<dyn LanguageModel>
   |                                             ^^^^^^^^^^^^^^^^^^^^^^ `LanguageModel` cannot be made into an object
   |
note: for a trait to be "dyn-compatible" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
  --> src/main.rs:26:26
   |
26 | pub trait LanguageModel: Clone + Send + Sync {
   |           -------------  ^^^^^ ...because it requires `Self: Sized`
   |           |
   |           this trait cannot be made into an object...
   = help: the following types implement the trait, consider defining an enum where each variant holds one of these types, implementing `LanguageModel` for this new enum and using it instead:
             OpenAILanguageModel
             OllamaLanguageModel
   = note: `LanguageModel` can be implemented in other crates; if you want to support your users passing their own types here, you can't refer to a specific type

error[E0038]: the trait `LanguageModel` cannot be made into an object

Is there a reason GenerateTextRequest contains the model? Your trait method already gets it with the &self parameter. Removing the Self argument from the GenerateTextRequest<Self> should then make the trait object-safe.

ai::generate_text() this method will call the model.generate_text() I expect ai::generate_text to implement tracing/retry logic so the trait such as OpenAI and Ollama will only do a simple call without retry logic. This way all traits don't need to update their logic and instead focus on the actual call. Might be to reduce confusion one could rename model.generate_text() to something like model.do_generate_text() or model.generate_text_raw() to avoid confusion with ai::generate_text().

I think I have found a solution that is a good compromise. I found a good discussion in one of the rust reddit post but can't seem to find it now. One way to solve it is to use Arc, the other way would be to use clone to box pattern. Since I lost the original reddit post. here is information I got from asking Claude.

System Prompts: explain in details clone to box pattern in rust step by step. tell me why we need it. performance impact. pros and cons. and provide example demonstrating the use of it. if clone to box pattern is not used how can it be done?

Let me explain the Clone-to-Box pattern in detail:

What is Clone-to-Box Pattern?
The Clone-to-Box pattern is a design pattern in Rust that allows you to clone trait objects that are stored in a Box. It's needed because Rust doesn't automatically implement Clone for trait objects (dyn Trait).
Why do we need it?


Trait objects can't implement Clone directly because:

The size of the concrete type isn't known at compile time
The Clone implementation might differ for each concrete type
Rust needs to know how to properly clone the underlying type




Performance Impact:
Pros:


Allows for dynamic dispatch while maintaining cloning capability
Memory safety is guaranteed
No runtime overhead beyond normal virtual dispatch

Cons:

Additional heap allocation due to Box usage
Slight performance overhead from dynamic dispatch
Memory fragmentation potential due to heap allocations


How it works:
a. Define a new trait that extends both the original trait and Clone
b. Implement a clone_box method that returns a boxed clone
c. Implement this trait for all types that implement both the original trait and Clone
Alternatives to Clone-to-Box:

a. Enum-based approach:

Pros:

Better performance (no dynamic dispatch)
No heap allocation needed
Compile-time type safety


Cons:

Not extensible (must modify enum for new types)
Can't handle unknown types
Code becomes more rigid



b. Serialization approach:

Pros:

More flexible
Can handle complex object graphs
Good for persistence


Cons:

Runtime overhead
Requires serde implementation
More complex



c. Factory pattern:

Pros:

More control over object creation
Can handle complex initialization
Better separation of concerns


Cons:

More boilerplate code
Might need type registration
Can be more complex to maintain




When to use Clone-to-Box:


When you need polymorphic behavior with cloning
When working with collections of trait objects
When the set of types isn't known at compile time
When dynamic dispatch is acceptable


When to avoid Clone-to-Box:


When performance is critical
When all possible types are known at compile time
When you don't actually need cloning
When working with small, fixed sets of types

The example in the artifact demonstrates:

Why normal trait objects can't be cloned
How to implement the Clone-to-Box pattern
How to use it in practice
Alternative approaches

Would you like me to explain any specific part in more detail or provide additional examples for any of the alternative approaches?
// First, let's define a trait that we want to use polymorphically
trait Animal {
    fn make_sound(&self) -> String;
}

// Implement concrete types
struct Dog {
    name: String,
}

impl Animal for Dog {
    fn make_sound(&self) -> String {
        format!("{} says: Woof!", self.name)
    }
}

struct Cat {
    name: String,
}

impl Animal for Cat {
    fn make_sound(&self) -> String {
        format!("{} says: Meow!", self.name)
    }
}

// Example 1: Without Clone-to-Box pattern
// This won't compile because trait objects don't implement Clone by default
struct Zoo {
    animals: Vec<Box<dyn Animal>>,
}

impl Zoo {
    fn new() -> Self {
        Zoo {
            animals: Vec::new(),
        }
    }

    // This won't work because we can't clone trait objects directly
    /*
    fn clone_animal(&self, index: usize) -> Box<dyn Animal> {
        self.animals[index].clone()  // Error: Clone is not implemented for dyn Animal
    }
    */
}

// Example 2: Using Clone-to-Box pattern
// First, define a new trait that extends Animal and Clone
trait CloneableAnimal: Animal + Clone {
    fn clone_box(&self) -> Box<dyn CloneableAnimal>;
}

// Implement CloneableAnimal for any type that implements both Animal and Clone
impl<T> CloneableAnimal for T
where
    T: Animal + Clone + 'static,
{
    fn clone_box(&self) -> Box<dyn CloneableAnimal> {
        Box::new(self.clone())
    }
}

// Now we can create a collection that supports cloning
struct BetterZoo {
    animals: Vec<Box<dyn CloneableAnimal>>,
}

impl BetterZoo {
    fn new() -> Self {
        BetterZoo {
            animals: Vec::new(),
        }
    }

    fn add_animal<T: CloneableAnimal + 'static>(&mut self, animal: T) {
        self.animals.push(Box::new(animal));
    }

    fn clone_animal(&self, index: usize) -> Box<dyn CloneableAnimal> {
        self.animals[index].clone_box()
    }
}

// Make our concrete types cloneable
#[derive(Clone)]
struct CloneableDog {
    name: String,
}

impl Animal for CloneableDog {
    fn make_sound(&self) -> String {
        format!("{} says: Woof!", self.name)
    }
}

#[derive(Clone)]
struct CloneableCat {
    name: String,
}

impl Animal for CloneableCat {
    fn make_sound(&self) -> String {
        format!("{} says: Meow!", self.name)
    }
}

// Example usage
fn main() {
    let mut zoo = BetterZoo::new();
    
    let dog = CloneableDog {
        name: String::from("Buddy"),
    };
    let cat = CloneableCat {
        name: String::from("Whiskers"),
    };
    
    zoo.add_animal(dog);
    zoo.add_animal(cat);
    
    // Now we can clone animals
    let cloned_animal = zoo.clone_animal(0);
    println!("{}", cloned_animal.make_sound());
}

// Alternative Approaches Without Clone-to-Box:

// 1. Using enum (when types are known)
enum AnimalEnum {
    Dog(CloneableDog),
    Cat(CloneableCat),
}

impl Clone for AnimalEnum {
    fn clone(&self) -> Self {
        match self {
            AnimalEnum::Dog(dog) => AnimalEnum::Dog(dog.clone()),
            AnimalEnum::Cat(cat) => AnimalEnum::Cat(cat.clone()),
        }
    }
}

// 2. Using serialization/deserialization
use serde::{Serialize, Deserialize};

#[derive(Clone, Serialize, Deserialize)]
struct SerializableAnimal {
    species: String,
    name: String,
}

// 3. Factory pattern
trait AnimalFactory {
    fn create(&self) -> Box<dyn Animal>;
}

Here is the final code that I got with clone to box pattern. Rust Playground

There are also crates that enabled this but it seems simple enough for me to just handcraft it. GitHub - dtolnay/dyn-clone: Clone trait that is object-safe