I'm surprised that this has never been proposed before!
I was thinking of putting this into a revision of my latest fixed-size array proposal, but it deserves separate discussion, at least for the search potential for others.
Basically, this idea brings bit fields from C. Besides improving conversion of C structures, we could use it to jam properties closer together.
/// A type that can be converted to and from a compact set of bits.
///
/// You can use any type that conforms to the `BitFieldRepresentable` protocol
/// for a property marked with the `@compact` attribute or as an associated
/// value for an enumeration case marked with that attribute. Several
/// internally simple types in the standard library conform to
/// `BitFieldRepresentable`, such as most numeric types and `Bool`. When a
/// containing type contains several members allowed to be compact, the compiler
/// may make several of them share a representation at the processor level,
/// reading and writing subsets of the shared representation's bits as needed to
/// realize a member at the user level.
///
/// Expressing a value of a type conforming to `BitFieldRepresentable` means
/// representing that value with a few bits as possible, while making sure each
/// distinct value of the conforming type maps to a distinct value of the
/// associated `CompactValue` type. Unless the number of values is a power of
/// two, there will be unmapped compacted values that should not be permitted
/// for reverse-mapping.
///
/// Conforming to the BitFieldRepresentable Protocol
/// ================================================
///
/// To use your own custom type as part of a bit field, add
/// `BitFieldRepresentable` conformance to your type.
///
/// The compiler automatically synthesizes your custom type's
/// `BitFieldRepresentable` requirements when you declare conformance in the
/// type's original declaration and the type meets these criteria:
///
/// - For a `struct`, all of its instance-level stored properties must conform
/// to `BitFieldRepresentable`.
/// - For an `enum`, none of its cases use associated values.
///
/// Note that a tuple type automatically conforms to `BitFieldRepresentable` if
/// all of its members do, and a grid array type conforms if its element type
/// does.
///
/// To customize your type's `BitFieldRepresentable` conformance, to adopt
/// `BitFieldRepresentable` in a type that doesn't meet the crieteria listed
/// above, or to extend an existing type to conform to `BitFieldRepresentable`,
/// implement the `init(compactValue:)` initializer and `compactValue` property
/// in your custom type.
///
/// For example, the following type can make do with automatic synthesis:
///
/// struct Color: BitFieldRepresentable {
/// let useRed: Bool
/// let useGreen: Bool
/// let manyBlue: (high: [2 ; Bool], low: [2 ; Bool])
/// }
///
/// The `Color` type can be used compactly anywhere the compiler can fit six
/// bits. A more complex type:
///
/// enum MoreColor {
/// case simple(Color)
/// case complex(Float16)
/// }
///
/// needs explicit work:
///
/// extension MoreColor: BitFieldRepresentable {
/// var compactValue: [17 ; Bool] {
/// var result: CompactValue = fill(false)
/// switch self {
/// case simple(let c):
/// result[0] = false
/// _ = result[1...6].copy(from: c.compactValue)
/// case complex(let f):
/// result[0] = true
/// _ = result[1...].copy(from: f.compactValue)
/// }
/// }
///
/// init?(compactValue: [17 ; Bool]) {
/// if compactValue[0] {
/// let compactColor = compactValue[1...6]
/// guard let color = Color(compactValue: compactColor) else {
/// return nil
/// }
///
/// self = .simple(color)
/// } else {
/// let compactFloat = compactValue[1...]
/// guard let float = Float16(compactValue: compactFloat) else {
/// return nil
/// }
///
/// self = .complex(float)
/// }
/// }
/// }
///
/// But now can be used compactly in a bigger type:
///
/// class MyApp {
/// @compact(Int32)
/// let colorParts: (base: MoreColor, extra: UInt8)
///
/// //...
/// }
protocol BitFieldRepresentable {
/// The type that can represent all values of the conforming type, using as
/// few composed Boolean states as possible.
///
/// Every distinct value of the conforming type has a corresponding unique
/// value of the `CompactValue` type, but there may be values of the
/// `CompactValue` type that don't have a corresponding value of the
/// conforming type.
associatedtype CompactValue: [_! ; Bool]
/// Creates a new instance from the specified compacted value.
///
/// If there is no value of the type that corresponds with the specified
/// compact value, this initializer returns `nil`. For example:
///
/// enum PaperSize {
/// case A2, A4, A5, Letter, Legal
/// }
///
/// print(PaperSize(compactValue: [false, false, false]))
/// // Prints "Optional(PaperSize.A2)"
///
/// print(PaperSize(compactValue: [true, true, true]))
/// // Prints "nil"
///
/// - Parameter compactValue: The compact value to use for the new instance.
init?(compactValue: CompactValue)
/// The corresponding value of the compacted type.
///
/// A new instance initialized with `compactValue` will be equivalent to
/// this instance. For example:
///
/// enum PaperSize: String {
/// case A2, A4, A5, Letter, Legal
/// }
///
/// let selectedSize = PaperSize.Letter
/// print(selectedSize.compactValue.reversed())
/// // Prints "[false, true, true]"
///
/// print(selectedSize == PaperSize(rawValue: selectedSize.compactValue)!)
/// // Prints "true"
var compactValue: CompactValue { get }
}
Where "[_! ; Bool]
" is the pseudo-protocol for one-dimensional fixed-size arrays of Bool
where the length has to be specified at compile time. (Any interface to FSAs besides single-element subscript is something I made on the fly and shouldn't be inferred to make it into another FSA proposal.) The "@compact
" attribute is something else made on the fly. The attribute goes before a stored property or a enumeration case. The type of the targeted member has to be BitFieldRepresentable
. When converted, the result has to fit within the type given in the attribute. The attribute can omit the containing type, in which the compiler will pick the best type.
If the conforming type is too large for any of default integer types, should a compile-time error be raised, or should the compact
attribute be ignored?
The big problem is making small integers. C can make its multi-bit bit fields act as integer types because all its integer types are built-ins, and so the C runtime can do the translations between calculations. Swift deliberately puts the default integer types into the standard library instead of being built-ins. Either we make Int0
, UInt0
, Int1
,... all the way to UInt63
, which seems impractical, or use auxiliary functions to give one-dimensional Bool
fixed-size arrays the integer operations. (Slapping FixedWidthInteger
on every Bool
FSA also seems like a bad idea.)
enum IntegerOperationsForBooleanGridArrays {
public static func add<T: [_! ; Bool, U: [_! ; Bool]>(unsigned t: T, unsigned u: U) -> some [_! ; Bool]
public static func add<T: [_! ; Bool, U: [_! ; Bool]>(unsigned t: T, signed u: U) -> some [_! ; Bool]
public static func add<T: [_! ; Bool, U: [_! ; Bool]>(signed t: T, unsigned u: U) -> some [_! ; Bool]
public static func add<T: [_! ; Bool, U: [_! ; Bool]>(signed t: T, signed u: U) -> some [_! ; Bool]
// Do the same for: subtract, multiply, divide, modulus, quotient & remainder, negate, comparisons, etc.
}
Or just force the user to pick default integer types to use and convert themselves for doing math operations.