Bounds; need explicit TryFrom in some cases?

I have a function that sends a request and returns a reply. The request and reply types are converted to/from an underlying line-based protocol using regular TryFrom.

pub async fn create_account<C, P>(
  frmio: &mut Framed<C, blather::Codec>,
  req: MkAccReq<P>
) -> Result<i64, Error>
where
  C: AsyncRead + AsyncWrite + Unpin + Send,
  P: Hash + Eq + AsRef<str>
{
  let reply: MkAccReply = sendrecv(frmio, req).await?;
  Ok(reply.id)
}

pub async fn list_accounts<C, P>(
  frmio: &mut Framed<C, blather::Codec>
) -> Result<Vec<AccInfo<P>>, Error>
where
  C: AsyncRead + AsyncWrite + Unpin + Send,
  LsAccReply<P>: TryFrom<Params, Error = ReqErr>  // <-- ?
{
  let req = LsAccReq;
  let reply: LsAccReply<P> = sendrecv(frmio, req).await?;
  Ok(reply.lst)
}

My question concerns the second bound in list_accounts(). That LsAccReply<P>: TryFrom<Params, Error = ReqErr> is not something I came up with myself. The compiler claimed that there exists no TryFrom<Params> implementation for LsAccReply<P> (which isn't true), and suggested that I add the bound. When I did, it accepted the code -- but I don't understand why.

No such bound is needed in create_account(). Is it related to the <P> in the return type (which isn't present in MkAccReply)?

What's the signature of sendrecv?

pub async fn sendrecv<C, O, T>(
  frmio: &mut Framed<C, Codec>,
  out: O,
) -> Result<T, Error>
where
  C: AsyncRead + AsyncWrite + Unpin + Send,
  O: TryInto<Telegram, Error = ReqErr>,
  T: TryFrom<Params, Error = ReqErr>
{
}

There's two ways to write each function, and you simply chose different ways for each.


For create_account:

MkAccReply implements TryFrom<Params> and has no generics so that doesn't need any bounds. MkAccReq must have an impl like

impl<P> TryFrom<MkAccReq<P>> for Telegram where P: Hash + Eq + AsRef<str>

or one with less bounds, or an equivalent TryInto impl.

So you don't need MkAccReq<P>: TryInto<Telegram> because the bounds on P are enough to tell that MkAccReq<P>: TryInto<Telegram> is already true.

You could write create_account with either of these bounds:

  • P: Hash + Eq + AsRef<str> (what you have now)
  • MkAccReq<P>: TryInto<Telegram, Error = ReqErr>

For list_accounts:

LsAccReq implements TryInto<Telegram> so it doesn't need any bounds. LsAccReply<P> must have an impl like:

impl<P> TryFrom<Params> for LsAccReply<P> where P: /* SOME BOUNDS HERE */

So you can write list_accounts with either of these bounds:

  • P: /* SOME BOUNDS HERE */
  • LsAccReply<P>: TryFrom<Params, Error = ReqErr> (what you have now)

These bounds are mostly the same, but I think I'd prefer the ones mentioning TryFrom/TryInto since those are actually about what the function needs. That way, if you change the bounds on the TryFrom impl, these functions won't need updating (where the functions are called might, though). But you may want to use the P bounds if you don't want the user to know TryFrom is involved, or just because they're a little easier to follow in documentation.

1 Like

I knew about the P: ... bound as used in the first case, and I tried to use it in the second case. But I realized now that I copied the wrong bounds list, which is why it didn't accept it.

I did not know about the other form, so I was quite confused when the compiler suggested it. It's actually pretty neat; some of these bounds lists are pretty long, and it's nice to not have to spell them out.

Learned something new due to failing a simple copy'n'paste. I'll count that as a win. Thank you!

1 Like