Skip to content

Unify structs and C structs #2422

Description

@oprypin

Follow-up to Allow adding methods to C structs

Wrappers of C libraries are currently hindered because C/lib structs are not full-featured and have very strange and buggy behavior when it comes to constructors (see #557), so in order to make a proper interface for the library one has to make another type that represents this struct and convert to and from this type. Note, however, that these small structs are often abundantly used in libraries, and things like a huge array of structs is not rare. Copying and converting such an array N times per second is not good for performance, and neither are the ubiquitous conversions between the two types for function arguments and return values.

lib L
  struct S
    a : Int32
    b : LibC::Char*
  end
end

struct S
  def initialize(@a : Int32, @b : LibC::Char*)
  end

  protected def initialize(s : L::S)
    @a, @b = s.a, s.b
  end
  protected def to_c : L::S
    L::S.new(a: @a, b: @b)
  end
end

I would like to note that currently structs and lib structs behave mostly the same, and have the same field layout, as this example shows:

str = "abc"
arr = [S.new(5, str.to_unsafe), S.new(7, str.to_unsafe)]

larr = [] of L::S  # needed because "can't execute `larr[1]` - Array(L::S) in `larr` was never instantiated"
larr = (pointerof(arr) as Pointer(Array(L::S))).value

p larr[1] #=> L::S(@a=7, @b=Pointer(UInt8)@0x435274)

Obviously, the example is vile, but my point is if the layout is the same then we should merge these two types into one thing.

The only problem is the ability to add fields anywhere in the code, which breaks everything.

struct S
  @c = ":("
end

So my suggestion is to make structs defined inside lib to be just like normal structs, but without the ability to add more fields to them. The syntax will also create the usual members in that struct, to not break backwards compatibility: initialize with 0 args that zeroes all fields, initialize with N args that sets all members, and property for each member. It is also important to be able to override all of these (maybe zeroing all bytes is not wanted, and maybe some special behavior for setters is wanted).

Sure, the inability to add fields still makes the structs "not full-featured" but how often does one really need to add fields to an existing type? This is a bad practice anyway.

The next step would be discussing what happens with inheritance. I think it should be possible to inherit from these structs and even add fields, as that doesn't affect interoperability. It would also be nice to have downcasting that drops the extra fields...

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions