A year in Rust. And I still can't read the docs!

Had no idea that could be done. Thanks for pointing that out

1 Like

Hmm, that seems to only show up on /tungstenite, not /tokio_tungstenite. cc @GuillaumeGomez, I think this is a bug in the search index.

I think it's linked to my other issue. Please open an issue so we don't lose track of it in the meantime.

1 Like

It turns out this is a duplicate of https://github.com/rust-lang/docs.rs/issues/494, it works fine normally but breaks with --no-deps and --extern-html-root-url. Sorry for the noise.

If anyone is interested in writing documentation for this, it would be a great first issue. Instructions here: https://github.com/rust-lang/rust/issues/75197

Perhaps.

But I don't understand what exactly is the issue I want fixed here. Never mind how to fix it.

"I don't understand anything in the Rust / Rust crates documentation ever anytime" does not sound like a good starting point.

I am tempted to raise an issue suggesting that the tokio_tungstenite guys provide a simpler WS client example that explains exactly what is going on and where all the parts used come from. And of course some usage description on the front page of the crate would help as well. Do they actually want people to use what they have made?

Over the last two years I use Rust I developed a certain algorithm to search through the docs if I run into a problem like yours. As pointed out sometimes there are complicated relations but in most cases it works. Maybe it can help you, too:

  1. Look up the type signature of the function to call to get the type required.
    • In your case this failed due to this trait extension stuff.
  2. Search the type's docs for ways to get an instance:
    • Constructor methods like new() or with_...()
    • Implementation of std::default::Default
    • Implementation of the standard conversion traits From, Into, TryFrom, TryInto and FromStr.
    • Helper function in the same or parent module
  3. Try to find examples
  4. Raise an issue at the crate's repository and ask for help
  5. Ask here

About use ...::Trait;

This is something I took as granted but in my local Meetup group there where also members that struggled with it as well.

The visibility (pub or not) of methods of a struct or enum is defined in the impl block(s). No matter where those impl blocks are, e.g. other modules, pub methods are visible to every usage of the type.

struct Foo;
mod foo { // 'privat' module
    impl super::Foo {
        // public function
        pub fn foo() { println!("Hi, from Foo!"); }
    }
}

fn main() {
    // Compiles even if the impl block for `foo()` is not visible to `main()`
    Foo::foo();
}

In contrast, to call a method from a trait implementation on a type the trait needs to be in scope, i.e. do a use ...::Trait; statement. In such cases, I prefer to do use ...::Trait as _; this shows that I only want to use the trait's methods and don't implement it in the module.

struct Foo;
pub mod bar {
    pub trait Bar { fn bar(); }

    // This block could also be located in a 'private' module anywhere in the crate.
    impl Bar for super::Foo {
        fn bar() { println!("Hi, from Foo in Bar!"); }
    }
}

// Required to be able to call `bar()` on Foo
// However, `Bar` is never used directly so we can do `as _`
use bar::Bar as _; 

fn main() {
    Foo::bar();
}

As a (late) birthday present here the playground link to both examples.

2 Likes

I find your algorithm hangs somewhere before step 4 unless a time out is implemented :slight_smile:

Your Foo::bar example is curious. Is there actually any real use for attaching a function like bar() to a struct like Foo{} if it has no self parameter and no way to make use of anything in Foo{} without being given a reference to a Foo{}? Such a reference would be a self by a different name, surely bar() may as well be a free standing function?

Or am I missing a point again?

Anyway, I was pondering that kind of situation with my JWT code:

    use jwt::SignWithKey;
    ....
    let mut claims = BTreeMap::new();
    claims.insert("name", "value");
    let token_str = claims.sign_with_key(&key).unwrap();

I would not blame anyone for being perplexed about that. How would they guess or find out what it does?

Unless it is commented the code say nothing about JSON web tokens. I prefer to move as much commenting into actual code as meaning names of variables, methods and such.

They won't find any method sign_with_key on a BTreeMap.

Luckily a google for rust sign_with_key turns up the jwt crate pretty quickly. But potentially there could be thousands of crates implementing the trait method one is looking for.

Of course there is that use jwt::SignWithKey; to be found at the top of he file. Lucky it has a similar name to glom onto. But it need not have.

Turns out I can remove the `use jwt::SignWithKey;' statement and write it explicitly like so:

    let token_str = jwt::SignWithKey::sign_with_key(claims, &key).unwrap();

Ah, that's better. A good old fashioned function with a name that describes what it does and parameters that tell what it does it to. No comments needed. And no need for that use :slight_smile:

Surely there is a way to do that with your Foo::bar(); example without needing the ugly Foo::bar(); ? I tried to do it but have not found the way yet.

1 Like

My example does not really make any sense as I tried to keep it as simple as possible. I just wanted to show the difference in visibility of a struct's own methods and the visibility of a trait's methods. Of course we could say the Bar::bar() should return a String or something.

I'm not sure what your problem is maybe this version of my example is easier to your eyes?

If you're struggling with Foo being a zero-sized type (which is not possible in C/C++) you might want a look at the Rust Embedded Book where they use zero-sized types to encode state at the type level.

No, no, Iike your example. It's clear enough.

I was just wondering if there is an actual practical use for attaching a method to a struct via a trait when the method has no self parameter and so cannot so anything within the stuct?

As it happens the fact that the sign_with_key() method takes a self method is what allowed me to rewrite it as an explicit function call and do away with the use jwt::SignWithKey;

I can do that with the Foo::bar example as it has no self parameter. So I was wondering, out of curiosity, what is the way to get rid of the use clause and call it explicitly? Surely this is possible some how?

<Foo as bar::Bar>::bar();

You’ll see this more often to refer to an associated type of Bar:

let mut x: <Foo as bar::Bar>::Result = Default::default();
if condition {
    x = bar::Bar::do_something(Foo { /* ... */ });
}

Consider me well confused now.

Do you have a complete example of that I can build and run so I might see the whole picture?

It’s a little silly, but I think it demonstrates everything: Playground


trait Bar {
    type Result: std::fmt::Debug;
    fn do_something(&self) -> Self::Result;
    fn bar() -> Self::Result;
}

struct Foo1;
struct Foo2(pub usize);

impl Bar for Foo1 {
    type Result = f32;
    fn do_something(&self)->f32 { 42.0 }
    fn bar()->f32 { 3.14159 }
}

impl Bar for Foo2 {
    type Result = usize;
    fn do_something(&self)->usize { self.0 }
    fn bar()->usize { 3 }
}

fn maybe<T:Bar>(bar: T, condition: bool) -> Option< <T as Bar>::Result >
                            // same as:  -> Option< T::Result >
{
    if condition {
        Some(Bar::do_something(&bar))
    } else {
        None
    }
}

fn main() {
    println!("{:?}", maybe(Foo1, true));
    println!("{:?}", maybe(Foo2(7), true));
    println!("{:?}", maybe(Foo1, false));
    println!("{:?}", maybe(Foo2(9), false));

    let x:<Foo2 as Bar>::Result = maybe(Foo2(4), true).unwrap();

    println!("\n{} {}", std::any::type_name::< <Foo2 as Bar>::Result >(), x);
    println!("{:?}, {:?}", <Foo1 as Bar>::bar(), <Foo2 as Bar>::bar());
}
1 Like

True, but programmers publish their code for the purpose of others using it. If you want me to use it, you will need to explain it. Except if one follows an elitist "if can't work it out you're not good enough for my code" path.

1 Like

Certainly documentation is a chore. Good documentation is hard. Most programmers are not keen on doing it and are pretty hopeless at it when they try.

I'm not sure I'm asking much in the way of documentation for tokio-tungstenite. But even just a simple, complete, example that works out of the box would probably get me going.

My woes with tokio-tungstenite continue...

Yesterday I spent hours running around docs and experimenting trying to get a tokio-taskstenite web socket client working. Even starting from the client example they have in the repo. At the end of it all it does work. It connects to my ws server and does what it should. By the way that ws server is also written in Rust using some synchronous ws library and has been in service for most of a year.

Feeling confident today I state on a tokio-tungstenite server. Again starting from the example they have in the repo. Oh no. Nothing builds. All kind of unintelligible errors. All kind of undefined things.

Ok, I thought, I'll create a fresh cargo project, set the deps in Cargo.tom as the docs say, paste the server code from the repo into main.rs and surely I can get it working as is.

Oh no. All kind of unintelligible errors. All kind of undefined things.

I start adding what the compiler tells me are missing dependencies to Cargo.toml . First 'tokio', that fixes something. Then 'futures-util', then 'futures-channel', getting closer.

The last missing item required 'tunstentite' itself.

BOOM it all explodes again!

Four hours later I find the problem. Adding 'tunstentite' is not the thing to do. What it requires is to fix the broken example itself. It has:

use tungstenite::protocol::Message;
type Tx = UnboundedSender<Message>;
type PeerMap = Arc<Mutex<HashMap<SocketAddr, Tx>>

Which should be:

use tokio_tungstenite::tungstenite::Message;
type Tx = UnboundedSender<Message>;
type PeerMap = Arc<Mutex<HashMap<SocketAddr, Tx>>

And have deps in Cargo.toml like so:

[dependencies]
futures-channel = "0.3"
futures-util = { version = "0.3", default-features = false, features = ["async-await", "sink", "std"] }
tokio = { version = "0.2.21", features = ["full"] }
tokio-tungstenite = "*"

I guess, all the wasted life is what we have to pay to get code for free. So we can't complain.

Or can we?

Would it be too much to ask to test what you put out and write half page of notes has to how to get it working?

Anyway, will be raising an issue on the tokio-tungstentite repo and have a bit of a rant whilst I'm there.

I still don't see why they are using channels form 'futures' rather than from 'tokio'.

use tungstenite::protocol::Message; works fine if you are using the same version of the tungstenite crate that tokio-tungstenite depends on (tungstenite = "0.10").

The reason it didn't work is that you were using a type from tungstenite 0.11 where the library expected a type from tungstenite 0.10. The compiler error you posted correctly suggested this, but unfortunately Cargo doesn't pass enough info to the compiler for it to offer a specific solution:

note: perhaps two different versions of crate `tungstenite` are being used?

This happened because tungestenite was updated in June with a breaking change, but tokio-tungstenite hasn’t updated to the new version yet. (It’s been updated on master, but the update isn’t published to crates.io yet.) If it were already using the latest tungstenite then you wouldn’t need the extra step of figuring out which old version to use.

Some ways to improve this situation could include:

  • Library authors could prominently document which versions of public dependencies they use.
  • Library authors should update their public dependencies as quickly as possible, so they are on the latest versions almost all of the time.
  • Cargo could provide better tooling for adding dependencies that are compatible with other crates you depend on.
  • rustc could print better error messages that say exactly which versions are different (requires additional integration between cargo and rustc).
5 Likes

By the way, one useful place to find the right library version is docs.rs. If you look at the Trait Implementations for WebSocketStream and click on the Message type, it links to tungstenite::Message from tungstenite 0.10.

I would much rather that tokio-tungstenite did not ask me to depend on 'tungstenite'. I'm doing all this so as to achieve a asyc web socket server. Why should I need to pull in a sync web socket library?

Whist we are at it :

I would be much happier if 'tokio-tunstenite', or at least it's examples, did not ask me to depend on futures and all that future, pin_mut, stream::TryStreamExt stuff.

Perhaps it would be useful to display the search tricks (which you get when pressing ?) as a dropdown below the search input is focused but empty.

Additionally it might help to focus the first non-empty tab of the results. In your example the first tab which is displayed by default has 0 results and the (6) on the 3rd tab is in light grey, so easy to miss.

(sorry, replied to the wrong post previously)

1 Like

Another option for the crate maintainers is to re-export dependencies that are part of the interface, in this way you could always use tokio_tungstenite::tungstenite instead of depending on it yourself. This is my personal preference, as cargo doesn't have a way to say "depend on the same version of this crate as this other crate".

3 Likes