fzyzcjy / flutter_rust_bridge

Flutter/Dart <-> Rust binding generator, feature-rich, but seamless and simple.

Home Page:https://fzyzcjy.github.io/flutter_rust_bridge/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Hashcode different every time Ulid returned

PrismaPhonic opened this issue · comments

Describe the bug

I use the Ulid library to generate ids for most of my objects. It appears in dart that anytime I call a .id() getter on one of my objects to return the Ulid and check the resulting hashcode, the hashcode is different every single time.

Steps to reproduce

  1. Mirror Ulid from the Ulid crate
  2. Use it in a struct as a field
  3. Add a getter for that field
  4. Call the getter in dart, with .hashCode after and observe that the hashcode changes on every call

Logs

Github is refusing to let me attach logs because they are too long

Expected behavior

I expect the hashcode to stay the same everytime it is called

Generated binding code

No response

OS

MacOS

Version of flutter_rust_bridge_codegen

No response

Flutter info

No response

Version of clang++

No response

Additional context

No response

Hi! Thanks for opening your first issue here! 😄

To add a little bit to this - I get a different hashcode every time even if I try to attach the freeze attribute

Hi, could you please share some minimal reproducible code?

Given https://github.com/ulid/spec#specification, it seems that ulid will be different for each call (because it has timestamp). So some code may be needed to see what is going on.

The Ulid in this case is already created - of course if I was generating a new ulid and comparing them, they would be different. UUID has a timestamp portion as well, for instance - it's just arranged differently.

pub struct WorkoutDetails {
    pub id: Ulid,
    pub name: String,
    pub exercise_info: Vec<ExerciseInfo>,
}

impl WorkoutDetails {
    pub fn id(&self) -> Ulid {
        self.id
    }
var id = workoud.id();
var id_again = workout.id();
print(id.hashCode);
print(id_again.hashCode);

I've also found that Mirroring doesn't work as the documentation describes. Providing a mirrored type for Ulid still results in WorkoutDetails being fully opaque. The only way to get around this is to create a wrapper type in the rust crate that's inside the flutter project that impls From<Ulid> and use that instead.

Here's how I've attempted mirroring:

#[flutter_rust_bridge::frb(mirror(Ulid))]
pub struct _Ulid(pub u128);

I've included the pub use per the documentation for the Ulid type as well.

As a side note, I've found it extraordinarily limiting to have to mirror anything that's not in the current crate (which apparently doesn't work anyways) - it's lead to me having to create duplicate types for tons of types in my core library. It would be odd for me to duplicate my core library to live inside the flutter project itself just to satisfy the generator - maybe I'm missing something here, but it's been a very clunky experience compared to other FFI libraries I've used

I was able to work around this. For added detail, here's what worked for me:

  1. I created an owned type in the Rust crate used to generate everything that I could freely to and from a Ulid:
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)]
pub struct FlutterUlid(pub [u8; 16]);

impl FlutterUlid {
    #[flutter_rust_bridge::frb(sync)]
    pub fn bytes(&self) -> [u8; 16] {
        self.0
    }
}

impl From<Ulid> for FlutterUlid {
    fn from(value: Ulid) -> Self {
        Self(value.to_bytes())
    }
}

impl From<FlutterUlid> for Ulid {
    fn from(value: FlutterUlid) -> Self {
        Ulid::from_bytes(value.0)
    }
}

Then I changed the id type in my structs to use FlutterUlid - this then resulted in non opaque types in dart land. It also turned out that in the process of doing so I had to remove all the getters I created since they overlapped with the actual field names and dart didn't like that the generated code had getters named identically to field name.

Then in Dart when I wanted to use the Ulid as a key to something, I did something along the lines of this:

  final HashMap<U8Array16, String?> selectedWeights = HashMap(
      equals: (key1, key2) {
        if (key1.length != key2.length) {
          return false;
        }

        for (var i = 0; i < key1.length; i++) {
          if (key1[i] != key2[i]) {
            return false;
          }
        }

        return true;
      },
      hashCode: Object.hashAll);

by calling bytes() on my custom FlutterUlid struct I was able to get out a U8Array16. Unfortunately it appears that dart considers every instance of these to be fundamentally different from a hashCode perspective, because it compares if the pointers point at the exact same object instance, rather than if the internal values match, so I had to create a custom hashmap for comparing the internal values. I imagine there's no equivalent to something like PartialEq in dart?

As a side note, I've found it extraordinarily limiting to have to mirror anything that's not in the current crate (which apparently doesn't work anyways) - it's lead to me having to create duplicate types for tons of types in my core library. It would be odd for me to duplicate my core library to live inside the flutter project itself just to satisfy the generator - maybe I'm missing something here, but it's been a very clunky experience compared to other FFI libraries I've used

One way is to use opaque types (instead of translatable types), then there is no need for mirroring. Secondly, IMHO the famous serde also requires (something similar to) mirroring for external types. Could you please show which library you have used that can understand 3rd party types, such that I can have a look.

Unfortunately it appears that dart considers every instance of these to be fundamentally different from a hashCode perspective

What about letting flutter_rust_bridge generate a freezed class (instead of a naive class), IIRC it may understand deep comparisons correctly.

I tried freezed as well and it failed. So, my experience with mirroring so far is that I get an opaque rust type with NONE of the methods. I would be more than happy if the methods came along for the ride, but without that, and without a way to generate the methods for mirrored types, the only utility the opaque rust type has is for handing it back to Rust.

The swift rust bridge library is an example of a library where I can get methods from external types, as long as I declare the method name in the extern block of my bindings crate for swift-rust-bridge.

The flutter rust bridge book seems to indicate that I should be able to get methods from opaque types, but my experience is that I have never gotten methods from opaque rust types that were derived from outside of my bindings library using flutter rust bridge. Is that a bug or expected behavior?

Is that a bug or expected behavior?

It is an unimplemented ;)

Originally I wanted to have fully automatic support for external types, i.e. users do not need to write down anything at all, just like what you do for internal types. However, that may not be very trivial, so instead there comes mirroring feature. Now there seems to be two choices in order to let mirrored types support methods etc:

  1. Require users to specify method signatures (like the swift bridge you mentioned, if I understand correctly) on mirrored types. (Easier)
  2. Or, let flutter_rust_bridge auto scan 3rd party crate, thus no need to manually do mirroring. (Harder, write down for brainstorming)

I would like to implement it hopefully within a few days.

Oh nice! Let me know when it's done. Option 1 would work well. This would be HUGELY useful for me, as I don't want to move my entire core library into a single crate inside my flutter project, so everything for me is an external type :-P

The hashcode/equals thing will be solved by #1860, mainly:

image

Methods of external crates: #1861

@PrismaPhonic Let me know when it's done.

Done (in 2.0.0-dev.31)!