-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Turn std.rand.Random into a Mixin #3785
Conversation
c2f314b
to
e087d5d
Compare
e087d5d
to
5718090
Compare
One thing that I want to check - maybe you can help me with this @daurnimator - is how does this affect bloat? For example, what is the size of the std lib test binary with everything as mixins (including streams and sd.rand.Random) vs with master branch? Also, what would be your plan for dealing with function name conflicts? |
You should get the error:
Test case: const Foo = struct {
pub fn a() void {}
};
const Bar = struct {
pub usingnamespace Foo;
pub fn a() void {}
};
pub fn main() void {
const x = Bar{};
} |
Right but do you see how this is problematic? There could easily be 2 interfaces that both have a |
Not really: if you wanted to have two different methods both called Good mixin design involves documenting the functions/fields you expect on the object, as well as the methods you add (note that we should get this latter part for free with documentation generation). |
Testing
After stripping:
Testing just
After stripping:
So... this decreased binary size. That was unexpected! |
Which build modes did you use? Can you try Debug, ReleaseFast, and ReleaseSmall? |
I personally consider this as a failure of mixins in language design. In every language with mixins, I wish there's a way to explicitly declare the dependencies at the consumption site as well as being able to push it into a separate namespace. Something like: # before
class Banana
include Enumerable
def each
# body
end
end
# after
class Banana
enum = include Enumerable(:each)
def each
# body
end
end |
So that's one reason why mixins take
I don't use this in the implementation today because I got stuck on #3699. But once fixed, this should be easy to do with zig.
You can do this today, but it brings #591 back into the picture. |
i.e. there is a minor increase in binary size for the stream branch. |
You may want to use google's Bloaty McBloatface tool to have an insight into why/how the binary sizes change. |
Interesting note: with the mixin style, you can get the old style back quite easily: const ArbitraryRandom = struct {
fillFn: fn (self: *ArbitraryRandom, buf: []u8) void,
pub fn bytes(self: *ArbitraryRandom, buf: []u8) void {
return self.fillFn(self, buf);
}
pub usingnamespace Random(ArbitraryRandom);
};
test "ArbitraryRandom" {
const MyPRNG = struct {
random: ArbitraryRandom,
next_value: u8,
pub fn init() @This() {
return .{
.next_value = 0,
.random = ArbitraryRandom {
.fillFn = fillFn,
},
};
}
fn fillFn(random: *ArbitraryRandom, buf: []u8) void {
const self = @fieldParentPtr(@This(), "random", random);
for (buf) |*b| {
b.* = self.next_value;
}
self.next_value +%= 1;
}
};
var r = MyPRNG.init();
expect(r.random.int(u0) == 0);
r.next_value = 0;
expect(r.random.int(u1) == 0);
expect(r.random.int(u1) == 1);
expect(r.random.int(u2) == 2);
expect(r.random.int(u2) == 3);
expect(r.random.int(u2) == 0);
} |
I think the Instead of having all "used namespaces" at one place at the top of the scope (eg in the typical |
@andrewrk, @daurnimator In Rust they can qualify with the traits's name if two traits have the same function. That can happen easily, it's not a bad design, the "bad luck" argument doesn't hold. |
This is another good example of providing another userland interface pattern (#1829) provide more insight into the possible language feature for this (#130). All of the things you noted are significant improvements over status quo. The downsides of this pattern compared to status quo are:
Let this example stand as a list of requirements when considering #130. It should solve all the issues that mixins solves, as well as the downsides I noted above. Until then, we'll stick with status quo. |
After chatting in IRC, I spied that
std.rand.Random
was a great candidate to be the first of what I've started calling "Mixin"s in the standard library.A "Mixin" is a new pattern where you have a function that takes a struct and returns a struct with no fields, intended to be used from the definition of another struct with
pub usingnamespace MyMixin(@This())
. This act of "mixing-in" adds "helper" methods to the struct you're currently defining, and are expect to take aself: var
parameter and use method(s) from the mixed-into struct. In this case ofRandom
, we expect the base struct to implement a methodbytes
that looks likepub fn bytes(self: ........., buf: []u8) void) void
.Compared to the existing pattern, a Mixin: