Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nonstandard StrConsumer trait #275

Open
rkuklik opened this issue Nov 18, 2024 · 16 comments
Open

Nonstandard StrConsumer trait #275

rkuklik opened this issue Nov 18, 2024 · 16 comments

Comments

@rkuklik
Copy link

rkuklik commented Nov 18, 2024

Hello. I was looking at base64::write::EncoderStringWriter, which requires StrConsumer. However, it is only implemented for String while looking basically as core::fmt::Write.

Could I either provide blanket impl<W: Write> StrConsumer for W (which shouldn't be breaking) or rip it out and switch to Write trait directly (breaking)?

@rkuklik rkuklik changed the title Default issue Nonstandard StrConsumer trait Nov 18, 2024
@rkuklik
Copy link
Author

rkuklik commented Nov 18, 2024

On second thought, both options would be breaking, but base64 isn't at vesion 1, so I would prefer trait replacement.

@marshallpierce
Copy link
Owner

The two traits do bear a superficial similarity, but they do have a different intent to communicate for the user: StrConsumer is just for base64, whereas fmt::Write is more general purpose (any Unicode), so I don't think it should just use fmt::Write directly. However, I agree that it should be possible for a user to use a fmt::Write as a StrConsumer.

I doubt a breaking change in this specific case is likely to affect much (any) calling code, so I'm not very concerned about that aspect, but I still prefer an explicit user choice for the purposes of confirming the user's intent to avoid accidentally using a type in an unexpected way. This could be done by making an adapter struct WriteConsumer<W> (or whatever). Then, if you want to use a Write as a StrConsumer, it's just a WriteConsumer::new(the_write) away.

@marshallpierce
Copy link
Owner

On the other hand, because there is only really one way that a Write could implement StrConsumer, maybe it is better to just do the blanket impl. Hmm... Competing priorities is what makes API design interesting.

@rkuklik
Copy link
Author

rkuklik commented Nov 18, 2024

The two traits do bear a superficial similarity, but they do have a different intent to communicate for the user: StrConsumer is just for base64, whereas fmt::Write is more general purpose (any Unicode), so I don't think it should just use fmt::Write directly. However, I agree that it should be possible for a user to use a fmt::Write as a StrConsumer.

I don't really see the difference in intent. Both represent types which can consume UTF-8 encoded bytes, while StrConsume doesn't offer any new capabilities or requirements (like Eq and PartialEq have). It is also implemented for String anyway, so I don't see any reason not to switch to Write. It just prevents me from using other types which tend to implement Write, but for which I can't provide trait impl.

I doubt a breaking change in this specific case is likely to affect much (any) calling code, so I'm not very concerned about that aspect, but I still prefer an explicit user choice for the purposes of confirming the user's intent to avoid accidentally using a type in an unexpected way. This could be done by making an adapter struct WriteConsumer<W> (or whatever). Then, if you want to use a Write as a StrConsumer, it's just a WriteConsumer::new(the_write) away.

I am unsure how this could be misused. I expect base64 to be valid string anyway. Before I discovered that this is even a thing, I created something similar here a went to see if there is an optimised version.

@rkuklik
Copy link
Author

rkuklik commented Nov 18, 2024

On the other hand, because there is only really one way that a Write could implement StrConsumer, maybe it is better to just do the blanket impl. Hmm... Competing priorities is what makes API design interesting.

To be honest, I would be for merging these two write Encoders into one, dropped the io::Write bound and added generic impls for io::Write and fmt::Write. But that is my preference, so no big deal.

Also, can base64 not be valid UTF-8? I saw the docs and wondered, why the perf hit? Because if it is, there is no need to use checked from_utf8.

@marshallpierce
Copy link
Owner

This library doesn't use unsafe, so we have to pay the cost. Fortunately the validation is pretty fast.

@marshallpierce
Copy link
Owner

To be honest, I would be for merging these two write Encoders into one

They serve different goals. One produces bytes, the other produces str/String.

@marshallpierce
Copy link
Owner

Unfortunately fmt::Write::write_str() returns Result, but StrConsumer::consume() is infallible, so there's not a safe way adapt a Write to a StrConsumer.

Can you explain more details about your use case? Maybe there's another way to provide an ergonomic solution.

@rkuklik
Copy link
Author

rkuklik commented Nov 19, 2024

This library doesn't use unsafe, so we have to pay the cost. Fortunately the validation is pretty fast.

It doesn't produce strings internally? If not, then there is no reason to add unsafe.

@rkuklik
Copy link
Author

rkuklik commented Nov 19, 2024

Unfortunately fmt::Write::write_str() returns Result, but StrConsumer::consume() is infallible, so there's not a safe way adapt a Write to a StrConsumer.

Can you explain more details about your use case? Maybe there's another way to provide an ergonomic solution.

The docs considered string formatting as infallible, apart from Formatter returning it, but makes sense if it seems bad to use it. As for the ergonomic solution, should I open a PR with my preferred API implemented, which could be discarded if it doesn't feel right?

@rkuklik
Copy link
Author

rkuklik commented Nov 19, 2024

To be honest, I would be for merging these two write Encoders into one

They serve different goals. One produces bytes, the other produces str/String.

I still don't get it, string is just UTF-8 encoded bytes, which I would expect is what base64 yields. So for me the difference is whether the consumer cares about that or not.

@marshallpierce
Copy link
Owner

As for the ergonomic solution, should I open a PR with my preferred API implemented, which could be discarded if it doesn't feel right?

Sure, go ahead, thanks.

@marshallpierce
Copy link
Owner

The docs considered string formatting as infallible, apart from Formatter returning it, but makes sense if it seems bad to use it.

🤔 Perhaps we could provide an adapter struct that makes it clear that it will panic on fmt::Error, so that while it's not automatic, it's at least not something users have to write themselves.

@rkuklik
Copy link
Author

rkuklik commented Nov 22, 2024

So I sent my first try, hope it makes sense.

@rkuklik
Copy link
Author

rkuklik commented Dec 8, 2024

Hello, could you please review my changes?

@marshallpierce
Copy link
Owner

I'll get to it when I can, just been busy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants