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

Wrapped primitives #2953

Closed
ghost opened this issue Jul 27, 2019 · 6 comments
Closed

Wrapped primitives #2953

ghost opened this issue Jul 27, 2019 · 6 comments
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@ghost
Copy link

ghost commented Jul 27, 2019

This idea comes from looking at #1595 and the kotlin feature inline class.

Basically a type of struct that contains a single instance value (of a primitive type), that at run-time is treated as the primitive it wraps. Hence, operators work as normal as long as no types are mixed up.

Maybe it could also be possible to wrap Vectors in addition to the "normal" primitives like int and float.

To distinguish it a bit from normal structs, I suggest the name zigbox as that's unlikely to be used as an identifier by third parties.

// defining the wrapped primitives
const Kilometer = zigbox(value: i32);
const Second = zigbox(value: i32){
  const getMinutes = fn(self: Second) Minute{
     return Minute(self.value/Second(60));
  }
}
const Minute = zigbox(value: i32);

//  Example usage
const a : Kilometer = Kilometer(3) + Kilometer(6) //operators still work

fn calculateDuration(speed_k : Kilometer, speed_s: Second, distance: Kilometer) Second{
  //access the value to be able mix different types when needed.
  return Second((speed_k.value/speed_s.value)*distance.value);
}

Syntax in the example is somewhat based on #2873 , but the main point should come across anyhow.

@andrewrk andrewrk added the proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. label Jul 29, 2019
@andrewrk andrewrk added this to the 0.6.0 milestone Jul 29, 2019
@ghost
Copy link

ghost commented Jul 31, 2019

I have been paying attention to recent conversations about encapsulation, embedding, and inheritance, and on the surface this looks like a sane compromise, whereby the whole fragile base class issue is avoided, and we don't introduce visibility modifiers for fields. It's all or nothing (as far as types having fields), just like things are now in the absence of this construct.

Something that bothers me about it however is that sometimes the interface of a type in a language like zig includes its size. If the size of u64 changed to something else, it could severely break a lot of code. We expect it to always have the same size. @OpaqueType does not introduce this problem when interfacing with c and hiding type information because its always refered to by pointer, and never by value.

Maybe this isn't really a problem at all, and relying on the size of a type that doesn't explicitly communicate it is a code smell. It just seemed to me that this might make the abstraction leaky in a very subtle way.

@ikskuh
Copy link
Contributor

ikskuh commented Jul 31, 2019

I really like your proposal, especially the point where i can add methods to my wrapped types.

A real world example would be an OpenGL API:

const gl = @cImport({
    @cInclude("GL/gl.h");
});

const Error = error { … };

pub fn getError() Error!void {
    const e = gl.glGetError();
    if(e != gl.GL_SUCCESS)
        return …;
}

const Shader = zigbox(c_uint) { … };
const Program = zigbox(c_uint) {
    pub fn attach(pgm : Program, sh : Shader) !void {
        gl.glAttachShader(@unbox(pgm), @unbox(sh));
        return getError();
    }
    pub fn detach(pgm : Program, sh : Shader) !void {
        gl.glDetachShader(@unbox(pgm), @unbox(sh));
        return getError();
    }
};

Also i would not explicitly name the field values and use builtin functions for boxing/unboxing (because it feels more "special" than just access a fiel named .value and thus would be a higher hurdle, at least for me):

const Second = zigbox( i32){
    const getMinutes = fn(self: Second) Minute{
        return @box(Minute, @unbox(self.value) / 60);
    }
}

@daurnimator
Copy link
Contributor

I really like your proposal, especially the point where i can add methods to my wrapped types.

I still think my proposal for non-exhaustive enums is best for a usecase like your GL example.
#1595 (comment)
@unbox in your example would be @enumToInt.

@ikskuh
Copy link
Contributor

ikskuh commented Aug 1, 2019

I still think my proposal for non-exhaustive enums is best for a usecase like your GL example.

Yeah, true. Maybe conversion operators (Kilometer.toMiles()) would be a better example as OpenGL objects are not arithmetic values.

@andrewrk
Copy link
Member

This proposal competes with #1595, and between the two I find #1595 to be the stronger proposal. That's not saying #1595 will be accepted; but it's enough to close this one.

@ghost
Copy link
Author

ghost commented Apr 22, 2020

This proposal was closed in favor of distinct types, #1595, but the use case here could be covered with typedef (proposal #5132 , which in turn is a generalization of distinct types ).

const gl = @cImport({
    @cInclude("GL/gl.h");
});

const Error = error { … };

pub fn getError() Error!void {
    const e = gl.glGetError();
    if(e != gl.GL_SUCCESS)
        return …;
}

const Shader = typedef(c_uint,.Alias) { … };
const Program = typedef(c_uint, .Alias) {
    pub fn attach(pgm : Program, sh : Shader) !void {
        gl.glAttachShader(pgm, sh);
        return getError();
    }
    pub fn detach(pgm : Program, sh : Shader) !void {
        gl.glDetachShader(pgm, sh);
        return getError();
    }
};

test "gl/typedef" {
  const shadertmp : c_uint = ... // init
  const programtmp : c_uint = ... // init
  const p : Program = programtmp; // coerce to typedef
  _ = p.attach(s);

}

If the typedef config .Distinct was used instead of .Alias in the typedef declarations, there would be some type safety too in addition to getting method syntax. See the typedef issue for context.

const Program = typedef(c_uint, .Distinct);

test "gl/typedef/distinct" {
  const pgm_tmp : c_uint = // init
  const pgm = @as(Program, pgm_tmp);
  glSomething(pgm) // expects c_uint, but 'Program' coerces down one level to c_uint automatically

  customGlFunctionInZig(pgm_tmp); // error. c_uint does not coerce up to 'Program' if
  //  that was the parameter type specified in 'customGlFunctionInZig'
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

No branches or pull requests

3 participants