-
Notifications
You must be signed in to change notification settings - Fork 13
Use a single Buffer type #298
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
base: main
Are you sure you want to change the base?
Conversation
This removes `GlyphBuffer` and `UnicodeBuffer`, replacing them with a single `Buffer` type to more closely match HB.
behdad
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with one comment inline. I'll comment separately on the assert vs error.
| /// | ||
| /// HarfBuzz calls this `hb_buffer_content_type_t`. | ||
| #[derive(Copy, Clone, PartialEq, Eq, Debug)] | ||
| pub enum BufferContentType { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HarfBuzz also has a content type Invalid. That's used when you reset the buffer. Lemme see how you handle that...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see, you use an Option of it. I think that works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this is simply more idiomatic when dealing with enums that have some sort of invalid state. We should probably do the same for the others.
| #[inline] | ||
| pub fn push(&mut self, codepoint: char, cluster: u32) { | ||
| if self.content_type == Some(BufferContentType::Glyphs) { | ||
| // TODO: return an error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function should be renamed to push_char or something. Otherwise it's hard to differentiate it from add.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While making changes, I took the opportunity to align this naming with the methods on the standard String type (I think we did the same when we renamed ensure -> reserve and other methods like push_str already match). I think this is the only caller for add so I can just inline the code to avoid confusion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We want to keep add(), for lowlevel buffer work.
Actually now I see a missing check in
I'm a proponent of assert / panic as these are preconditions. Errors that can never happen in correctly-constructed code end up just polluting the code IMO. |
Making apis look infallible when they are fallible is hostile and violates the expectation that if your Rust code compiles it's likely to work. Ideally we would structure the types so the error can't happen - nice if possible - so we don't need assert. If not then the failure is a valid outcome and Error seems correct, the user gets to decide what to do if they held it wrong and is made explicitly aware that it's possible to hold it wrong. |
So.. you don't believe in invariants, then? |
To me, this just shifts the problem to the user, since in a correct program they'd end up just unwraping, panicing, or ignoring the error. It is useful only when one is writing the program. To normal runs of the program it's just more nuisance in the client code all over. |
|
I believe in invariants and prefer my invariants to be encoded in the type
system / enforced by the compiler. If we can't do that here for some reason
then just admit it, return a Result. The user will have another ? In their
code, it's fine.
Panics can change the impact from bad request to a downed job. Imagine a
world where a lot of libraries chose to intake bad inputs and panic on
them. It would *suck*, having Result where error is possible is
dramatically better. Making the user handle more error conditions is
something Rust gets right IMHO.
…On Wed, Nov 12, 2025, 6:59 PM Behdad Esfahbod ***@***.***> wrote:
*behdad* left a comment (harfbuzz/harfrust#298)
<#298 (comment)>
Ideally we would structure the types so the error can't happen - nice if
possible - so we don't need assert. If not then the failure is a valid
outcome and Error seems correct, the user gets to decide what to do if they
held it wrong and is made explicitly aware that it's possible to hold it
wrong.
To me, this just shifts the problem to the user, since in a correct
program they'd end up just unwraping, panicing, or ignoring the error. It
is useful only when one is *writing* the program. To normal runs of the
program it's just more nuisance in the client code all over.
—
Reply to this email directly, view it on GitHub
<#298 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABRKXADPVVJFXOSSKA3HTN334PXXRAVCNFSM6AAAAACL52RO2WVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZTKMRUHE3TMNRSHE>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
Ah, good catch, thanks. |
|
I can appreciate fail-fast error handling but our attitude toward this in Rust code so far has been to avoid panics except for the occasional internal invariant that we feel certain we've checked elsewhere. Note that Rust std does have some methods that panic but they're almost always accompanied by companion methods that return option or result. I image there are ways to keep most of the benefits of this change while also using the type system to check some invariants. For example, something like: // or BufferFillMode::Append which would clear the buffer only if it currently contains glyphs
buffer.fill(BufferFillMode::Replace).push_str("hello");I'm not sure what to do about segment properties. Having them attached to the buffer means that they can always be unset or disagree with the requested shape plan. Making them required configuration rather than buffer properties solves this but takes us even further from HB. |
This removes
GlyphBufferandUnicodeBuffer, replacing them with a singleBuffertype to more closely match HB.Pros:
Option<UnicodeBuffer>andtake()to reuse buffersCons:
TODOs: