Skip to content

Conversation

@dfrg
Copy link
Collaborator

@dfrg dfrg commented Nov 12, 2025

This removes GlyphBuffer and UnicodeBuffer, replacing them with a single Buffer type to more closely match HB.

Pros:

Cons:

  • Larger number of methods so proper usage is less discoverable
  • More methods are now fallible since they depend on the current content type

TODOs:

  • Probably missed some content type checks but I think I found the important ones
  • Replace asserts with errors... deferring since this PR is already large enough

This removes `GlyphBuffer` and `UnicodeBuffer`, replacing them with a single `Buffer` type to more closely match HB.
@dfrg dfrg requested a review from behdad November 12, 2025 22:00
Copy link
Member

@behdad behdad left a 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 {
Copy link
Member

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...

Copy link
Member

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.

Copy link
Collaborator Author

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
Copy link
Member

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.

Copy link
Collaborator Author

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.

Copy link
Member

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.

@behdad
Copy link
Member

behdad commented Nov 12, 2025

  • Probably missed some content type checks but I think I found the important ones

Actually now I see a missing check in guess_segment_properties as well. HB has assert_unicode there.

  • Replace asserts with errors... deferring since this PR is already large enough

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.

@rsheeter
Copy link
Contributor

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.

@behdad
Copy link
Member

behdad commented Nov 13, 2025

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.

So.. you don't believe in invariants, then?

@behdad
Copy link
Member

behdad commented Nov 13, 2025

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.

@rsheeter
Copy link
Contributor

rsheeter commented Nov 13, 2025 via email

@dfrg
Copy link
Collaborator Author

dfrg commented Nov 13, 2025

Actually now I see a missing check in guess_segment_properties as well. HB has assert_unicode there.

Ah, good catch, thanks.

@dfrg
Copy link
Collaborator Author

dfrg commented Nov 13, 2025

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.

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

Successfully merging this pull request may close these issues.

4 participants