Making a non-disturbing polite language proposal

I see 3.3K items in the ziglang issues and do not want to spam that. However sometimes I feel the urge to do a little proposal. Would this one be crazy?

Following the latest ArrayList - ArrayListUnmanaged changes, we see an
empty constant defined inside the unmanaged versions:

pub const empty: Self = .{
    .items = &.{},
    .capacity = 0,
};

Wouldnt it be nice the have sprinkled here and there an empty const defined in the std library where appropriate? For example the std.bit_set.IntegerBitSet.

const bits = std.bit_set.IntegerBitSet(64);

const x = bits.empty; // nicer.
const x = bits.initEmpty(); // less nice.

A related question (me bit twiddling in my program).
Are there convenient ways to test / set / clear bits? Builtins would be nice.
Or are there somehwere hidden in the std some functions?
Sometimes I used the std bitset but other times a need raw masks.

1 Like

std.bit_set.IntegerBitSet is just a wrapper around a mask int so you can easily turn your raw mask into a bit set and use its API at any point.

This is not a language proposal. It’s standard library proposal and they are much more welcome. If you make a PR implementing this changes it will probably be merged.

5 Likes

I never did that. Afraid to mess up :slight_smile:
I’ll ask my intelligent daughter to help

2 Likes

bits should be title case because its a type.

const Bits = std.bit_set.IntegerBitSet(64);
const x : Bits = .empty;

And I think it is better/nicer to read when you use decl-literals.

Having defined constants for .empty and .full would be nice.

I think the regular approach would be to add the new constants and document the old functions as deprecated, which then would be removed in the next release cycle after the new way was added.

yes that is true

This works but could it be less anytyped?

pub fn test_bit(mask: anytype, pos: anytype) bool
{
    const masktype = @TypeOf(mask);
    const bitcount = @bitSizeOf(masktype);
    const bitset: std.bit_set.IntegerBitSet(bitcount) = .{ .mask = mask };
    return bitset.isSet(pos);
}

You could make pos a usize or even a std.math.Log2Int(@TypeOf(mask)).
Also const bitcount = @typeInfo(@TypeOf(mask)).int.bits is probably safer than @bitSizeOf().
And you could do .{ .mask = @bitCast(mask) } to be agnostic towards signedness if that’s desired (e.g. if mask is a c_int).

So put together:

pub fn test_bit(mask: anytype, pos: std.math.Log2Int(@TypeOf(mask))) bool
{
    const bitcount = @typeInfo(@TypeOf(mask)).int.bits;
    const bitset: std.bit_set.IntegerBitSet(bitcount) = .{ .mask = @bitCast(mask) };
    return bitset.isSet(pos);
}

But I think that’s the best you can get without an explicit comptime T: type.

You could assert std.meta.hasUniqueRepresentation(@TypeOf(mask)) and use @bitSizeOf(mask) that way the function could also be used with for example packed structs.

2 Likes

A weakness of the IntegerBitSet is that it returns usizes for bits. I think that is wrong.

Yeah that is indeed a little akward, I suspect that it’s done like this to to keep the APIs of StaticBitSet and DynamicBitSet as similiar as possible.

If you’re using wrapper functions anyways it should always be safe to @truncate the returned usize to a Log2Int though.

Yes, true.
But these are the reasons I wanna bake my something more lowlevel testbit, setbit, clearbit, get_lsb, clear_lsb etc.

Assuming you mean usize is used for capacity/indexes, removing the usize usage seems like a reasonable change to me:

pub const CapacityInt = std.math.IntFittingRange(0, size);

pub const IndexInt = std.math.IntFittingRange(0, size -| 1);

pub const bit_length: CapacitySize = size;

pub fn count(self: Self) CapacityInt {
    // ...
}

pub fn set(self: *Self, index: IndexInt) void {
    // ...
}

pub fn findFirstSet(self: Self) ?IndexInt {
    // ...
}

// etc

Making BitSetIterator not return usize would take a bit more work, though.

I think in this specific case, calling them .zeroes and .ones would be the way to go.

Decl literals are fairly new, it’s not surprising they aren’t totally pervasive in the standard library yet. It’ll get there.

Meanwhile, this works:

const Bits = std.bit_set.IntegerBitSet(64);
const x : Bits = .initEmpty();

Which is already a bit nicer. But since it’s doing nothing which can’t be accomplished with a decl literal, that would be better.

2 Likes

I personally would really love to see plain 'ol slices get the same treatment, though I realize this would be beyond the scope of a standard library change.

It would be so much nicer and more expressive than the &.{} syntax, which I have never learned to like.

I never did that. Afraid to mess up :slight_smile:
I’ll ask my intelligent daughter to help

Most important rule for creating a Github PR: after forking the upstream project, do your changes in its own uniquely named branch and create the PR from that branch, and don’t reuse branches for other PRs, instead delete the branch after the PR has been merged.

Makes both your life and the root project maintainer’s life easier :slight_smile:

2 Likes

Yeah. I first have to go through learning how to use all that PR stuff!
This month I have an insane amount of database conversion stuff to do for my job. So if anyone feels the urge to put these zeroes and ones (yes better naming that is @mnemnion) in feel free to do that.
There goes my chance for immortality.

1 Like