flutter_rust_bridge icon indicating copy to clipboard operation
flutter_rust_bridge copied to clipboard

What about constructors?

Open Dampfwalze opened this issue 4 months ago • 4 comments

Rust does not have a built-in feature for constructors, but the norm is to define an associated function new(...) for that type.

Dart has constructors as a feature. One can define up to one constructor and multiple named constructors. (A factory acts like a static method, returning an instance of a class, but has the same Syntax as a constructor to the class user)

Currently, on the Dart side, there is a constructor being generated that initializes all fields on the Dart side. But sometimes, the constructor on the Rust side is mandatory.

Solution

If a constructor on the Rust side should be used, generate a factory constructor that calls that constructor and do not expose the default constructor to the public API, if the struct does not have attribute #[frb(default_constructor)].

What should be considered a constructor?

  • An associated function should be considered a constructor,
    • if it does not have attribute #[frb(no_constructor)] and
    • if its signature is pub fn new(...) -> Self, where Self can also be the struct type, or
    • if it returns Self or the struct type and has attribute #[frb(constructor(default))]
  • An associated function should be considered a named constructor,
    • if it does not have attribute #[frb(no_constructor)] and
    • if it returns Self or the struct type and its name is not new. The name of the constructor should be
      • <name>, if it has attribute #[frb(constructor("<name>"))], or
      • the name of the function, otherwise
    • if its signature is pub fn new(...) -> Self, where Self can also be the struct type and it has attribute #[frb(constructor("<name>"))], where the name of the constructor is <name>.

Alternatives

Try to ignore the Dart constructor and only call MyStruct.newMyStruct(...). This is bad, if you have a public API

Additional context

Example
pub struct Point {
    pub x: f64,
    pub y: f64,
}

impl Point {
    // Constructor
    pub fn new(x: f64, y: f64) -> Point {
        Self { x, y }
    }

    // Named constructor
    pub fn all(v: f64) -> Point {
        Self { x: v, y: v }
    }

    // Renamed named constructor
    #[frb(constructor("with_magnitude"))]
    pub fn with_length(x: f64, y: f64, length: f64) -> Point {
        let scale = length / (x * x + y * y).sqrt();
        Self {
            x: x * scale,
            y: y * scale,
        }
    }

    // Not a constructor
    #[frb(no_constructor)]
    pub fn add(a: &Point, b: &Point) -> Point {
        Self {
            x: a.x + b.x,
            y: a.y + b.y,
        }
    }
}

Dampfwalze avatar Feb 16 '24 18:02 Dampfwalze