datrs / merkle-tree-stream

A stream that generates a merkle tree based on the incoming data.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Constrain Node as: From<PartialNode>

yoshuawuyts opened this issue · comments

commented

Feature Request

Summary

Constrain the HashMethods::Node type as Node + From<PartialNode>, and remove the node method.

Motivation

If we can constrain the Node type in HashMethods to be From<PartialNode>, we can remove an extra method, and remove a piece of boilerplate required for setting up DefaultNode. It also creates a more Rusty API.

Expected Behavior

Define HashMethods::Node as:

pub trait HashMethods {
  type Node = Node + From<PartialNode>;
}

and remove fn node(); from the trait.

Drawbacks

A custom Node implementation now requires a trait to be implemented, which might be a bit confusing for people new to Rust. But it'll result in less work for people using DefaultNode, and a slight API change for people using custom Node implementations.

Unresolved Questions

None.

I'm happy to spend some time this week on this enhancement idea. I'm still a little new to Rust, so I'll reach out when I have questions or need some guidance.

commented

@scotttrinh fantastic! 🎉

@yoshuawuyts

I've spent a little time here, but I'm having a hard time figuring out a generic way to impl From<PartialNode> for DefaultNode without taking the hash as an argument. Any direction you can point me in?

commented

@scotttrinh dang yeah, actually I think you might be right! -- not sure it's possible :(

commented

Oh actually: we could make this happen is if PartialNode had an Option<Hash> field. Because all information is then contained in the struct, converting using From<> would work!

commented

Or alternatively: we could create a new struct HashPair that contains both a PartialNode and a Hash, which is only ever used as an internal type unless you want to implement your own Node type.

DefaultNode would then have a From<HashPair> impl.

A few questions to consider:

  • Would this work for tuples? Would that be better?
  • What should we call this type? HashPair doesn't mention Node at all. PartialNodeWithHash would be more accurate, but doesn't exactly roll of the tongue. Suggestions?
  • Should this be part of any prelude? We should probably introduce a prelude at this point, haha.
commented

Fwiw, it looks like it's possible to convert from a tuple: https://play.rust-lang.org/?version=stable&mode=debug&edition=2015&gist=2c4ed43b253a06a357c92dd3a7e2724d.

Although to be honest I think a struct might be a lot cleaner. We'd just have to name it though.

Some other struct name ideas:

  • NodeParts
  • PartialHashPair
  • PartialWithHash

Coming from the other side: What about making a FromPartial trait like what is already implemented in DefaultNode with the from_partial method? I believe we can still define Into. Are there any advantages with using the standard From trait that we wouldn't get by implementing our own?

commented

Ohhh, I like NodeParts a lot! It describes what a Node is pretty well I think!


Are there any advantages with using the standard From trait that we wouldn't get by implementing our own?

Excellent question! I have a few thoughts:

  • type Node already needs to implement trait Node. I don't think there's currently any scenario where a type needs to implement one, but not the other.
  • In which case we'd probably have a from_partial method required on the Node. I already kind of feel like our Node trait is more of a grab bag of methods, more than a coherent interface. This would add to that.
  • People in the wider Rust ecosystem are used to having conversion traits exist as From, or now also the newer TryFrom traits. For example std::str::FromStr exists, but it feels a bit more redundant now that TryFrom basically implements the same behavior, but works on every type.
  • Which feels like it ties into a wider point: it seems most of the Rust ecosystem is converging on implementing TryFrom as the "works everywhere solution" to converting between types, which in turn can call down to Try, FromStr and others if it can also be converted without errors. I think it would make a lot of sense for us to not introduce any new abstractions, but instead stick to the standard conventions as much as possible.

Does that reasoning make sense?


It was pointed out on Twitter that there's prior art for implementing From for a tuple: https://github.com/hyperium/hyper/blob/master/examples/hello.rs#L13. Whether that's a good idea still remains a question, haha.

It was pointed out on Twitter that there's prior art for implementing From for a tuple: hyperium/hyper:examples/hello.rs@master#L13.

Followed that rabbit trail a little and it looks like they named that tuple pieces in std::net::SocketAddr impl for Into/From.

I think it would make a lot of sense for us to not introduce any new abstractions, but instead stick to the standard conventions as much as possible.

I 100% agree that following language convention is the way to go. 👍 If it comes down to tuple vs. struct, maybe it's worth looking around a bit more for other examples?

Random thought: Is there a pattern in the community of implementing From for both? Should be easy to do. In the cases where it improves readability or construction, you can use the struct, otherwise just inline the tuple?

commented

In the cases where it improves readability or construction, you can use the struct, otherwise just inline the tuple?

Hmm, I don't think that'd work here. We'd have to constrain the type parameter as Node + From<Pieces> + From<(PartialNode, Self::Hash)> to accept all variants. What we could do is:
impl From<(PartialNode, T)> for Pieces or something. But at that point I kind of wonder if it's worth it. I think we should probably pick one of the other, and I'm personally more inclined to having a named struct here.