1

I'm translating a C++ codebase that makes heavy use of interface classes to Rust. The interfaces abstract away implementation detail of network connections. I tried using generics and bound traits, but the generic type definitions have begun to take over my whole codebase! How do I 'stop' the spread of generic type definitions?

Because this is supposed to be a drop-in replacement and because I think the C++ solution is quite elegant I'm trying to stay close to the original implementation. I thought I could use traits to implement interface like functionality.

For example, the C++ codebase does something along these lines:

class iConnection
{
    virtual bool send_data(const std::vector<uint8_t>& bytes) = 0;
    virtual std::vector<uint8_t> receive_data() = 0;
}

class tcp_connection : iConnection
{..} // handle tcp connection implementation details
class http_connection : iConnection 
{..} // handle http connection implementation details

class Multiplexer
{
    std::vector<std::shared_ptr<iConnection>> connections;
    ...
    void send_data(const std::vector<uint8_t>& bytes)
    {
        for(auto& ptr: connections)
        {
            if( ptr->send_data(bytes) == true )
                break;
        }
    }
}

Note how the 'generic/trait' definition is only present in the definition of the connections member of the Multiplexer class.

I've tried creating a similar structure in Rust. The code I have now is something along these lines:

pub trait iConnection {
    fn send_data(&mut self, bytes: Vec<u8>) -> Result<(), ()>;
}

impl iConnection for TcpStream {
    fn send_data(&mut self, bytes: Vec<u8>) -> Result<(), ()> {
        self.Write(&bytes[..]);
    }
}

impl iConnection for MyHttpClass {
    fn send_data(&mut self, bytes: Vec<u8>) -> Result<(), ()> {
        self.send_request(bytes);
    }
}

pub struct Multiplexer<T: iConnection> {
    pub connections: Vec<Box<T>>,
}
// this seems like a potential place to stop the spread.
// If there's a way for the multiplexer not to be generic
// That'd solve my problem.
impl<T: iConnection> send_data for Multiplexer<T> {
    // use the connections
}

// ...

#[no_mangle]
pub extern "C" fn network_router_create<T: iConnection>() -> *mut Multiplexer<T> {
    Box::into_raw(Box::new(Multiplexer::new()))
}

This would be fine if I didn't need to interface with this library from the existing C++ codebase (to use it as a drop-in replacement). For obvious reasons an exported, unmangled function cannot be generic, so my question is: how do I stop the spread of 'genericness' without having to duplicate code that deals with a connection object's implementation details?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
user513647
  • 105
  • 1
  • 14
  • Do you need run-time dispatch, or compile time? ie. does the multiplexer need to contain connections of different types in the same instance, or would they all be the same type? – harmic May 15 '19 at 01:05
  • 1
    Idiomatic Rust uses `snake_case` for variables, methods, macros, fields and modules; `UpperCamelCase` for types and enum variants; and `SCREAMING_SNAKE_CASE` for statics and constants. Rust does not prefix traits with a special word; use `Connection` instead, please. – Shepmaster May 15 '19 at 02:53
  • Please take the time to write syntactically valid Rust code. Like C++, you cannot arbitrarily sprinkle semicolons and expect the code to compile. – Shepmaster May 15 '19 at 02:54

0 Answers0