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

Should Centi abd Milli implement Default? #15

Open
chungwong opened this issue Jan 27, 2025 · 5 comments
Open

Should Centi abd Milli implement Default? #15

chungwong opened this issue Jan 27, 2025 · 5 comments

Comments

@chungwong
Copy link

chungwong commented Jan 27, 2025

Given a struct Foo

// Cannot derive Default because Centi didn't implement Default
#[derive(Default)]
struct Foo {
  paths: Paths
}

the trait `std::default::Default` is not implemented for `Centi`, which is required by `clipper2::Paths: std::default::Default`
@chungwong chungwong changed the title Should Paths implement Default? Should Centi implement Default? Jan 27, 2025
@chungwong chungwong changed the title Should Centi implement Default? Should Centi abd Milli implement Default? Jan 27, 2025
@tirithen
Copy link
Owner

tirithen commented Jan 28, 2025

Hi! I ran some short experiments and found it a bit tricky to find a solution that works without specifically selecting scaling. The reason for the scaling is that Clipper2 is implemented with integer math to not get floating point rounding errors. I then implemented a mechanic to choose the scaling precision but left the API as f64 values only.

What you can do with the current released version is to manually implement default for Foo:

        struct Foo {
            paths: Paths,
        }

        impl Default for Foo {
            fn default() -> Self {
                Self {
                    paths: Paths::new(vec![]),
                }
            }
        }

        let foo = Foo::default();

I experimented with adding derived or manual Default traits but I only got some variants to pass:

    // TODO: this test fails util a way is found to have the compiler infer the default Centi scaler
    // #[test]
    // fn test_default() {
    //     let paths = Paths::default();
    //     assert_eq!(paths.len(), 0);
    // }

    #[test]
    fn test_default_deci_precision() {
        let paths = Paths::<Deci>::default();
        assert_eq!(paths.len(), 0);
    }

    #[test]
    fn test_default_as_struct_field() {
        #[derive(Default)]
        struct Foo {
            paths: Paths,
        }

        let paths = Foo::default();
        assert_eq!(paths.paths.len(), 0);
    }

I'm out of time at the moment, but please feel free to suggest concrete improvements. The best would be if adding a custom point scaler was optional and using only Paths::default() and similar was enough for anyone that wants to use the default Paths scaler.

It is odd to me that the compiler complains about let paths = Paths::default(); but accepts the struct field case after implementing Default for the related types:

        #[derive(Default)]
        struct Foo {
            paths: Paths,
        }

        let paths = Foo::default();

@chungwong
Copy link
Author

chungwong commented Jan 28, 2025

After seeking help from the discord channel, that is the best I come up with.
For whatever reason, the inference only works when we do this

#[test]
fn test_default() {
   //          vvvvvvv adding the angle brackets
   let paths = <Paths>::default();
   assert_eq!(paths.len(), 0);
}

Or the type needs to be specified

#[test]
fn test_default() {
   let paths: Paths = Paths::default();
   assert_eq!(paths.len(), 0);
}

@tirithen
Copy link
Owner

Exactly, it seems like the inference works much better when wrapped as a struct field.

I have never seen the <Paths>::default() syntax before, it's like an inverted turbofish, but yes it works, odd. Anyhow that syntax is probably not expected by many users.

Though maybe requiring let paths: Paths = Paths::default(); is as close as we can get? It still feels a bit verbose but I find it hard to find a way to satisfy both let paths = Paths::default(); and let paths = Paths<Deci>::default() at the same time.

Is suppose that we could go for just adding the Default traits then to the minimal types needed to unlock this API? It will solve your specific problem with the Foo struct field so there at least it looks good.

@chungwong
Copy link
Author

As far as I know, let paths = Paths::default(); will never work as the type is unkown and I think it has something to do with how Rust treats unspecific generic as the default, for more, see https://discord.com/channels/442252698964721669/448238009733742612/1333929247223451791

In short,

#[derive(Default)]
struct Foo {
    paths: Paths,
}

let paths = Foo::default();

and

let paths: Paths = Paths::default();

Are the same as the type Paths has been specified. However, with let paths = Paths::default();, Rust is not going to infer the default generic. And to my limited knowledge <Paths>::default() has something to do with how generic and trait bounds work.

With that said, with my limited understanding, I don't see how it will become a trouble if Centi and its friend has Default implemented.

@tirithen
Copy link
Owner

I added the defaults impls here cc77f7b and released the crate as 0.5.1.

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