How to find right trait?

Hi, I'm new about generics and traits. I know what they are but I have problems to find right trait for generics. Is there a easy way to find out what trait can combine my needs for generics ?

I checked source codes of crates but I'm not good at to understand them well.
For example, I was trying to implement tls support for websocket, and I wanted to make my functions work for both ws and wss. After couple of hours trying, found this trait and it works like a charm.

async fn stream<
    T: futures_util::Stream<Item = Result<Message, Error>> + std::marker::Unpin,
>(
    mut ws_stream: T,
)

I know it's not the right way to find proper trait. That's why I'm asking. How to find right traits in more logical way ?

The trait bound usually relates to the kinds of operations you need to be able to do inside of the function, and the kinds of types you want to instantiate it with. So a possible logical way is to have an implementation of your function that works for two (or more) concrete types already, and have (dummy) use-cases that call them. Then you'd try to merge both functions into one, using generics in places where the types differ. Finally, you can often follow compiler errors for hints for the trait bounds necessary; though that might not work perfectly for method calls (because method call syntax in turn desugars by using the available trait bounds).

To illustrate, if you have

fn print_i32(x: i32) {
    println!("{x}");
}

// contains the same code, just different type signature
fn print_str_ref(x: &str) {
    println!("{x}");
}

fn user() {
    let x: i32 = 42;
    print_i32(x);
    let x: &str = "hello";
    print_str_ref(x);
}

and want to turn this into a single generic print_anything, you can see x has different types, so let's use a generic and first make sure we have our use-case set up correctly

fn print_anything<T>(x: T) {
    todo!()
}

fn user() {
    let x: i32 = 42;
    print_anything(x);
    let x: &str = "hello";
    print_anything(x);
}

now we fill in the implementation, which will give compilation errors. While addressing those, we also make sure that the use-case doesn’t break:

fn print_anything<T>(x: T) {
    println!("{x}");
}
error[E0277]: `T` doesn't implement `std::fmt::Display`
 --> src/lib.rs:2:15
  |
2 |     println!("{x}");
  |               ^^^ `T` cannot be formatted with the default formatter
  |
  = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
  = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider restricting type parameter `T`
  |
1 | fn print_anything<T: std::fmt::Display>(x: T) {
  |                    +++++++++++++++++++

In this case, the compiler suggestion is already on-point and this works:

fn print_anything<T: std::fmt::Display>(x: T) {
    println!("{x}");
}

fn user() {
    let x: i32 = 42;
    print_anything(x);
    let x: &str = "hello";
    print_anything(x);
}

Though of course, it isn’t always that easy.


As for your use-case, you could have tried the same general approach; feel free to share your actual code, and maybe we can point out strategies to cope with issues that could have come up.

3 Likes

Thanks a lot, I don't want to bother too much that's why this is just the some part of my code.
Fullcode: radioxide/back/src/streaming.rs at dev · Tahinli/radioxide · GitHub

while let Ok((tcp_stream, listener_info)) = listener_socket.accept().await {
        let new_listener = Listener {
            ip: listener_info.ip(),
            port: listener_info.port(),
        };
        if relay_configs.tls {
            let streamer_tcp_tls = acceptor.accept(tcp_stream).await.unwrap();
            let wss_stream = tokio_tungstenite::accept_async(streamer_tcp_tls)
                .await
                .unwrap();
            let listener_stream_task = tokio::spawn(stream(
                new_listener,
                wss_stream,
                buffered_producer.subscribe(),
            ));
            let _ = listener_stream_tasks_producer
                .send(listener_stream_task)
                .await;
        } else {
            let ws_stream = tokio_tungstenite::accept_async(tcp_stream).await.unwrap();
            let listener_stream_task = tokio::spawn(stream(
                new_listener,
                ws_stream,
                buffered_producer.subscribe(),
            ));
            let _ = listener_stream_tasks_producer
                .send(listener_stream_task)
                .await;
        }
        println!("New Listener: {} | {:#?}", listener_info, timer.elapsed());
    }

async fn stream<T: futures_util::Sink<Message> + std::marker::Unpin>(
    listener: Listener,
    mut ws_stream: T,
    mut buffered_consumer: Receiver<Message>,
) {
    while let Ok(message) = buffered_consumer.recv().await {
        if buffered_consumer.len() > MAX_TOLERATED_MESSAGE_COUNT {
            println!(
                "{} Forced to Disconnect | Reason -> Slow Consumer",
                format!("{}:{}", listener.ip, listener.port)
            );
            break;
        }

        match ws_stream.send(message).await {
            Ok(_) => {
                if let Err(_) = ws_stream.flush().await {
                    println!(
                        "{} is Disconnected",
                        format!("{}:{}", listener.ip, listener.port)
                    );
                    break;
                }
            }
            Err(_) => {
                println!(
                    "{} is Disconnected",
                    format!("{}:{}", listener.ip, listener.port)
                );
                break;
            }
        }
    }
}

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.