How to deal with generic type proliferation?

Disclaimer: There rage in this question accumulated over a year.

TLDR: Is possible to encapsulate Session<RoundRobin<TcpConnectionPool<NoneAuthenticator>>> into Box<Session<IReallyDontCareHowYouSolveYourProblems>>

Many libraries use a long tree of generics to resolve their dependencies. For instance:

cdrs cassandra driver:
Session<RoundRobin<TcpConnectionPool<NoneAuthenticator>>>,

rs-tui:
Terminal<TermionBackend<AlternateScreen<MouseTerminal<RawTerminal<Stdout>>>>>

Now, if you try to send those references around, I need to pass all those types, or create an alias, or convert it into a generic.

This is especially painful if you try to use a trait to split abstractions to be able to compose it dynamically, like:

trait Repository { fn get(id: u32) -> String }
struct FileRepository { base_path: String }; impl Repository for FileRepo { .. }
struct CassandraRepository { session : Session }; impl CassandraRepository for Cassandra { ... }

fn do_stuff(repo: Box<dyn Repository>) { repo.get(0) }

All the the code I found around, are using hardcoded alias:

type CurrentSession = Session<RoundRobin<TcpConnectionPool>>;

That basically means, If want not to use a Static password, I need to re-compile my whole application and generate a new build. I don't even understand how can I support password/anonymous configuration without 2 different builds.

If you choose the generic path, now you need to define your structures as generic too.

pub struct CassandraRepository
{
session: Session,
}

A now, everywhere where I try to use or store this struct, I need a type. And this keeps going and going until you end one type for each library and compiler errors that you have no clue how to workaround.

This is something I really get frustrated with every time I try to building anything using any complex library.

I reject to accept that is just the way to do it. I should be able to change configuration without have a completely different build, I should be able to add new libraries without the need to change every function in my program to support new T.

Now come to my question. Can I somehow encapsulate this and hide the inner generic types?

struct CassandraSession {
    session: Box<dyn Session<_>>
}

Yes, this is one way:

Notice how, as preferred, Apple contains no type parameters, but contains a Box<dyn Hello> which by the trait definition of Hello requires the trait Astronaut. I can thus call the trait functions within Astronaut given only a Box<dyn Hello>

1 Like

I not sure if I understand how this can helps me. How this code would help me to encapsulate Session<RoundRobin<TcpConnectionPool>>?

Are you implying I should create my own trait with Self: Session<RoundRobin<TcpConnectionPool>> and implement/forward each operation?

Not sure if was your recommendation, but, thanks! One step forward.

I "just" need to reimplement all the interfaces of all my libraries, at least I can workaround. My current code

extern crate cdrs;

use cdrs::authenticators::{NoneAuthenticator, StaticPasswordAuthenticator};
use cdrs::cluster::session::Session;
use cdrs::cluster::{session, ClusterTcpConfig, NodeTcpConfigBuilder, TcpConnectionPool};
use cdrs::load_balancing::RoundRobin;
use cdrs::query::*;

type SS = Session<RoundRobin<TcpConnectionPool<NoneAuthenticator>>>;

trait AnySession {
    fn query(&self, s: &str);
}

impl AnySession for Session<RoundRobin<TcpConnectionPool<NoneAuthenticator>>> {
    fn query(&self, s: &str) {
        unimplemented!()
    }
}

impl AnySession for Session<RoundRobin<TcpConnectionPool<StaticPasswordAuthenticator>>> {
    fn query(&self, s: &str) {
        unimplemented!()
    }
}

fn do_stuff(session: &dyn AnySession) {
    let create_ks: &'static str = "CREATE KEYSPACE IF NOT EXISTS backend WITH REPLICATION = { \
                                 'class' : 'SimpleStrategy', 'replication_factor' : 1 };";
    session.query(create_ks) // .expect("Keyspace create error");
}

fn main() {
    let pass: Option<(String, String)> = Some(("".to_string(), "".to_string()));

    let session: Box<dyn AnySession> = if let Some((user, pass)) = pass {
        let auth = StaticPasswordAuthenticator::new(user, pass);
        let node = NodeTcpConfigBuilder::new("127.0.0.1:9042", auth).build();
        let cluster_config = ClusterTcpConfig(vec![node]);
        Box::new(
            session::new(&cluster_config, RoundRobin::new()).expect("session should be created"),
        )
    } else {
        let auth = NoneAuthenticator {};
        let node = NodeTcpConfigBuilder::new("127.0.0.1:9042", auth).build();
        let cluster_config = ClusterTcpConfig(vec![node]);
        Box::new(
            session::new(&cluster_config, RoundRobin::new()).expect("session should be created"),
        )
    };
    // do_stuff(&session);
    println!("done");
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.