{"action":"created","issue":{"url":"https://api.github.com/repos/crystal-lang/crystal/issues/2529","repository_url":"https://api.github.com/repos/crystal-lang/crystal","labels_url":"https://api.github.com/repos/crystal-lang/crystal/issues/2529/labels{/name}","comments_url":"https://api.github.com/repos/crystal-lang/crystal/issues/2529/comments","events_url":"https://api.github.com/repos/crystal-lang/crystal/issues/2529/events","html_url":"https://github.com/crystal-lang/crystal/issues/2529","id":151507779,"number":2529,"title":"What to do with struct inheritance","user":{"login":"asterite","id":209371,"avatar_url":"https://avatars.githubusercontent.com/u/209371?v=3","gravatar_id":"","url":"https://api.github.com/users/asterite","html_url":"https://github.com/asterite","followers_url":"https://api.github.com/users/asterite/followers","following_url":"https://api.github.com/users/asterite/following{/other_user}","gists_url":"https://api.github.com/users/asterite/gists{/gist_id}","starred_url":"https://api.github.com/users/asterite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/asterite/subscriptions","organizations_url":"https://api.github.com/users/asterite/orgs","repos_url":"https://api.github.com/users/asterite/repos","events_url":"https://api.github.com/users/asterite/events{/privacy}","received_events_url":"https://api.github.com/users/asterite/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":15,"created_at":"2016-04-27T22:35:30Z","updated_at":"2016-05-03T14:28:14Z","closed_at":null,"body":"Right now struct inheritance is possible. For example:\r\n\r\n```crystal\r\nstruct Point2\r\n  getter x, y\r\n\r\n  def initialize(@x = 0, @y = 0)\r\n  end\r\nend\r\n\r\nstruct Point3 < Point2\r\n  getter x, y, z\r\n\r\n  def initialize(x = 0, y = 0, @z = 0)\r\n    super(x, y)\r\n  end\r\nend\r\n```\r\n\r\nRegardless of whether the above makes semantic sense (let's not discuss this), struct inheritance has a big issue.\r\n\r\nYou'd expect Point2 to have two members, x : Int32 and y : Int32, so its total size is 8 bytes. You'd expect Point3 to have three members and a total size of 12 bytes. That currently is like that, no problem so far.\r\n\r\nHowever, let's say we have an array of Point2:\r\n\r\n```crystal\r\npoints = [] of Point2\r\n```\r\n\r\nAn array has a size, capacity and a buffer holding the elements. You'd expect the buffer to have Point2 elements next to each other, and since each Point2 occupies 8 bytes they'll be next to each other, so every 8 bytes would belong to a different Point2 object. However...\r\n\r\n```crystal\r\npoints = [] of Point2\r\npoints << Point3.new\r\n```\r\n\r\nThe above is valid, because `Point3 < Point2`. But Point3 occupies 12 bytes! Not only that, but if we want to know whether we have a Point2 or Point3 inside the array's buffer we also need to store something else: the type id. Suppose we use an Int32 for the type id. In total every element inside the array's buffer to be 16 bytes long. This is probably unexpected, because if Point2 occupies 8 bytes and I want an array of them, I don't want them to suddenly occupy double space (or more, imagine someone extends Point3 with more fields).\r\n\r\nC# has structs too, and they don't support inheritance. The [reason](http://stackoverflow.com/questions/1222935/why-dont-structs-support-inheritance) is basically what I just explained (in the [official docs](https://msdn.microsoft.com/en-us/library/aa664473(v=vs.71).aspx) they also mention structs can't be inherited).\r\n\r\nNow, C# allows compiling a library and then linking against it. The generated code must know in advance the size of a struct inside an array. If you let someone extend a struct then the generated code will be wrong.\r\n\r\nIn Crystal we compile a program by analyzing all its source code, so we can effectively know whether a struct was inherited and make an array of such structs occupy the necessary size. However, if we go that route it will mean that in the future we will never be able to generate small libraries and link them together separately. Still, the issue of an array of structs being bigger than what you'd expect is a separate issue that should be given some thought.\r\n\r\nSo, we must decide what to do with this. We have these choices:\r\n\r\n1. Disallow struct inheritance. The exception would be primitive-like types like `Value`, `Struct` and `Int`, etc., which kind of use inheritance (it's mostly conceptual here) but you can't have an array of `Value`, `Struct` or `Int`. In the future you'll anyway be able to have an array of `Object`, and if you put a struct inside it, it will be boxed.\r\n2. Allow struct inheritance but only for abstract classes. Non-abstract classes won't be inheritable. The Point2/Point3 example won't compile. An array of a non-abstract struct will only contain such structs so the size of the buffer elements can be known in advance. For the case of an abstract struct, and in order to provide binary compatibility, we'll probably want to represent it as a `{type_id, pointer_to_value}` in memory to provide binary compatibility (now that I think of it, modules should probably also be represented in this way). \r\n3. Allow full struct inheritance. This will mean that if you have an array of `struct Foo` you can't expect the size to be what you expect because someone might inherit your struct. We could add a `final` annotation to prevent that. But compiling a piece of code and linking it later won't be possible anymore.\r\n\r\nI personally would like 1 to happen. A struct is meant to be used when you either have an immutable type, or a kind of type for which you care about its memory size and know its layout. You wouldn't want its size to change under the hood.\r\n\r\nIt also simplifies the language and its implementation. If you need an abstract struct from which you have many inherited structs and then you need an array of such abstract struct, you can use a module instead. Or use an alias of all struct types (an alias is better to prevent adding more types being added, because a module can be included in other types too).\r\n\r\nIn case you wonder, for reference types (classes) this isn't a problem because a class is represented as a pointer to which data is stored. The first member behind that pointer is a type_id. That's why unions of class types, and even unions of class types and nil, can be represented with a single pointer, so the elements of an array of such types always occupy the size of a pointer (nil is encoded as the null pointer).\r\n\r\nAlso note that right now the compiler doesn't handle well an array, or proc, of a struct type when it has subtypes. #2382 and #2527 are cases of this.\r\n\r\nThis issue is not new to me and @waj, but it's time we take a decision.\r\n\r\n/cc @waj @bcardiff "},"comment":{"url":"https://api.github.com/repos/crystal-lang/crystal/issues/comments/216546295","html_url":"https://github.com/crystal-lang/crystal/issues/2529#issuecomment-216546295","issue_url":"https://api.github.com/repos/crystal-lang/crystal/issues/2529","id":216546295,"user":{"login":"asterite","id":209371,"avatar_url":"https://avatars.githubusercontent.com/u/209371?v=3","gravatar_id":"","url":"https://api.github.com/users/asterite","html_url":"https://github.com/asterite","followers_url":"https://api.github.com/users/asterite/followers","following_url":"https://api.github.com/users/asterite/following{/other_user}","gists_url":"https://api.github.com/users/asterite/gists{/gist_id}","starred_url":"https://api.github.com/users/asterite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/asterite/subscriptions","organizations_url":"https://api.github.com/users/asterite/orgs","repos_url":"https://api.github.com/users/asterite/repos","events_url":"https://api.github.com/users/asterite/events{/privacy}","received_events_url":"https://api.github.com/users/asterite/received_events","type":"User","site_admin":false},"created_at":"2016-05-03T14:28:14Z","updated_at":"2016-05-03T14:28:14Z","body":"Reopening because something's missing in the implementation."}}