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

Implement traits for slices, arrays, and tuples #735

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

BroderickCarlin
Copy link
Contributor

@BroderickCarlin BroderickCarlin commented Nov 8, 2023

Thank you for helping out with embedded-graphics development! Please:

  • Check that you've added passing tests and documentation
  • Add a CHANGELOG.md entry in the Unreleased section under the appropriate heading (Added, Fixed, etc) if your changes affect the public API
  • Run rustfmt on the project
  • Run just build (Linux/macOS only) and make sure it passes. If you use Windows, check that CI passes once you've opened the PR.

PR description

When evaluating e-g and related crates, it was noticed that working with collections of items required either writing a custom composite type or relying on a crate's own custom solution (such as embedded-layout's pseudo-linked list Chain). My impression was that this felt cumbersome and lead to overly verbose code, especially when attempting to do some basic rapid prototypes.

This PR implements many of the core traits for slices, arrays, tuples (up to 12 items), &T and &mut T, where appropriate and possible.

The main driver for this change is to enable better support for both homogeneous and non-homogeneous collections of elements that can be treated as if they were a single logical graphical element. This should also enable higher level crates to more easily support collections (again, both homogeneous and non-homogeneous) without the need for custom collection types or dynamic dispatch.

EDIT: this PR has been split and the implementations for references is now housed in #736

pub struct Pixel<C>(pub Point, pub C)
where
C: PixelColor;
impl<T: Drawable> Drawable for &T {
Copy link
Contributor

@kpreid kpreid Nov 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about making drawing work on slices?

// add ?Sized
impl<T: Drawable + ?Sized> Drawable for &T {...}

// add this impl
impl<T: Drawable> Drawable for [T] {...}

I saw [T] impls for other traits but not Drawable, and all of the reference impls could have ?Sized added.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drawable, unfortunately, cannot be implemented for slices without either:

  • changing the trait (did not want to introduce any breaking changes in this PR)
  • having the associated Output type of the Drawable trait not be meaningful, or lossy, when implemented on slices.

This is the same for the lack of slice implementations on other traits which return a success value from one or more of their trait methods.

With respect to the ?Sized addition, I'd be fine to add this for the traits and implementations in which it makes sense and it feels like a worthwhile change

@rfuest
Copy link
Member

rfuest commented Nov 9, 2023

Interesting, but I'm a bit hesitant to add so many trait implementations for generic types.

In my projects the Drawable objects are almost always very short lived and I wouldn't benefit from storing them in an array or a tuple. Can you provide a code example that shows how the changes in this PR would help in the use case you had in mind?

@BroderickCarlin
Copy link
Contributor Author

BroderickCarlin commented Nov 9, 2023

Interesting, but I'm a bit hesitant to add so many trait implementations for generic types.

Can you elaborate on what may be causing the hesitation? The implementations for &T and &mut T are purely for ergonomics and should, if anything, improve the developer experience. Also noting that these are very common default implementations across the Rust ecosystem for the purpose of improving ergonomics and I'm not sure there is a downside to their implementation. An example of what these implementations enable:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=5e9e77a46815774e18f6499eea962f14

Tuples, Slices, and Arrays are all added as a means to group homogeneous or non-homogeneous items and perform operations across the group. These are implementations that cannot be done outside of this crate without introducing new traits or types, which has the ramification of fragmenting the embedded graphics ecosystem. Using Tuples, Slices, or Arrays also enables developers to write much more succinct and maintainable code. A simple toy example of this can be seen here:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=f7dd58aaf4d522523df1811ed7e2bd82

Beyond this, I've been exploring writing a layout crate heavily inspired by embedded-layout. My goal for this project has been to provide an API that looks something along the lines of:

let view = Column(
  Layout::TightCenter,
  (
    text,  
    Row(Layout::SpaceBetween, (triangle, circle)),
    (triangle2, circle2)
  )
);

which would be analogous to embedded-layout's

let view = LinearLayout::vertical(
        Chain::new(text)
            .append(LinearLayout::horizontal(Chain::new(triangle).append(circle)).arrange())
            .append(
                Chain::new(triangle2.align_to(&circle2, horizontal::Center, vertical::Top))
                    .append(circle2),
            ),
    )
    .with_alignment(horizontal::Center)
    .arrange();

However, currently because of the lack of support for any sort of grouping within e-g utilizing core primitives I would need to fall back to providing some sort of collection type - just like embedded-layout has done with their Chain.

I think it is also worth mentioning that collections of types would imply that more of the graphical elements are stored in memory at any one time, causing an increase in memory utilization. Personally, I don't see this as a issue given that this change in no way would preclude individuals from writing custom composite elements that internally utilize short-lived primitives, just like is done today.

@rfuest
Copy link
Member

rfuest commented Nov 10, 2023

Can you elaborate on what may be causing the hesitation?

Not really, it just seemed like a lot of changes and I want to be sure we don't make any changes that could cause issues in the future. But perhaps this is only caused by the fact that I haven't done a lot of Rust development lately and I'm extra cautious.

Thanks for the great explanation. I didn't have time to look into this in detail yet, but I think it would be better to split this into two PRs, one for the trait implementations for references and another for the arrays and tuples. IMO the trait impls for refeferences could be merged first and the collections might need some more discussion. I would also like to hear @jamwaffles opinion on the impls for the collections.

@BroderickCarlin
Copy link
Contributor Author

BroderickCarlin commented Nov 10, 2023

Went ahead and split this into 2 PRs, with #736 housing the implementations for references.

I'd be happy to answer any other questions as well or share the small layout crate, if that would help. Otherwise I look forward to hearing the others thoughts

@BroderickCarlin BroderickCarlin changed the title Implement traits for slices, arrays, tuples, immutable refs, and mutable refs Implement traits for slices, arrays, and tuples Nov 10, 2023
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.

None yet

3 participants