13 Named types — type, newtype, range, cast
What to know first
struct and fieldLooking back
Chapter 4 said usize and u64 do not convert automatically even on machines where they have the same width. Why?
A. To make places mixing address counts with plain numbers visible in the source. When the meaning differs, the type differs even if the representation is the same, and crossing over has to be written. This chapter covers how users apply the same principle themselves — newtype — and how to write ranges, widths and layouts into types.
The need for this chapter, and its context
u64, swapping them goes unnoticed by the compiler. So does passing 300 to a parameter meant for a percentage. These defects arise because a type says only its representation and not its meaning. As the last chapter of Part III, it gathers the ways to attach names, ranges and layouts to types. It sits right before contracts (Part IV) because range has the same standing as a contract.By the end of this chapter
type is another name for the same type and newtype a different type with the same representation, and that cast crosses between them. You will pick up what range lo hi in a parameter position gives the caller and the body, and how cast differs from widen and narrow. You will also see bits integers from 1 to 64 bits, and layout packed and view, which pin down byte layout.The questions this chapter answers
- Does a
newtypecost anything at run time?
13.1 type is an alias, newtype a new type#
type <name> <type> . gives an existing type another name. The two names are the same type and interchangeable.
examples/ch13/aliases.low
module aliases .
rem run: area 3 4
rem run: half_percent 90
type meters u64 .
type pct range 0 100 .
fn area input w meters . input h meters . output u64 .
requires le w 1000000 .
requires le h 1000000 .
do
return mul w h .
end
fn half_percent input p pct . output u8 .
do
return narrow u8 (div p 2) .
end
Output
$ lowentc --run area aliases.low 3 4
area(3, 4) = 12
$ lowentc --run half_percent aliases.low 90
half_percent(90) = 45
meters is just another name for u64, so the result of mul w h can be returned as a u64. Aliases shorten long types (type bytes slice u8 .) or leave meaning in the source. pct is an alias for a type with a range attached, and every place that takes this name inherits the range (covered below).
A type declaration has no be.
examples/ch13/type_be.low
module type_be .
rem expect: E-TYPE-DECL
type pct be u8 .
Output
$ lowentc --check type_be.low
type_be.low:4:0 E-TYPE-DECL: a type alias is `type N T .` and a newtype is `newtype N T .` — without `be`. `be` binds a VALUE (`let x u8 be 1 .`); a type declaration names a TYPE. One meaning, one spelling
be is the word with which let and var bind values. What is bound here is a type. One meaning, one spelling.
newtype <name> <type> . makes a different type with the same representation as an existing one.
examples/ch13/ids.low
module ids .
rem expect: E-TYPE-NOMINAL
newtype user_id u64 .
newtype order_id u64 .
fn load_order input id order_id . output u64 .
do
return 0 .
end
fn mistake input u user_id . output u64 .
do
return load_order u .
end
Output
$ lowentc --check ids.low
ids.low:14:0 E-TYPE-NOMINAL: these are NOMINALLY DISTINCT types with the same representation — a `newtype` (or usize/isize) is not interchangeable with its base or with another newtype. Convert explicitly with `cast <type> <value>` (RFC-0002 §6.3.1)
user_id and order_id are both represented as 64-bit numbers, but cannot be swapped. Such mistakes are hard to find with tests — when the numbers happen to coincide, the wrong order is fetched without a sound. Split them with newtype and translation catches it.
type meters u64 . newtype user_id u64 .
meters ──┐ user_id ◀── cast ──▶ u64
├──▶ u64
u64 ─────┘ same representation, two types
two names, one type swapping them is E-TYPE-NOMINAL
nothing stops a mix every crossing shows as a castWhen you mean to cross over, write cast.
examples/ch13/ids_ok.low
module ids_ok .
rem run: roundtrip 42
newtype user_id u64 .
fn user_of input n u64 . output user_id .
do
return cast user_id n .
end
fn raw_of input u user_id . output u64 .
do
return cast u64 u .
end
fn roundtrip input n u64 . output u64 .
do
return raw_of (user_of n) .
end
Output
$ lowentc --run roundtrip ids_ok.low 42
roundtrip(42) = 42
With only one or two crossing ops like user_of and raw_of, the places where a raw number enters as a user_id gather into those two. Those are the only places that need checking.
Q. Does a newtype cost anything at run time?
A. No. The representation is the same, so in native code user_id is just a 64-bit integer. The distinction exists only at translation time, and cast user_id n does not change the value. It is a free distinction, worth using freely.
13.2 range — a contract that became the shape of a parameter#
Writing range <low> <high> in a parameter’s type position makes that parameter accept only values between the two ends, inclusive.
examples/ch13/ranges.low
module ranges .
rem run: scale 100
rem trap: scale 101
fn scale input a range 0 100 . output u8 .
do
return narrow u8 (mul a 2) .
end
Output
$ lowentc --run scale ranges.low 100
scale(100) = 200
$ lowentc --run scale ranges.low 101
== ir diagnostics (1) ==
0:0 E-VM-CONTRACT: a parameter's declared range was violated at the program boundary (the value entered from outside — no caller proved it)
The body of scale uses the fact that a is at most 100. So mul a 2 does not exceed 200, narrow u8 cannot fail, and both overflow checks are removed. Keeping to the range is the caller’s responsibility. If the type of the value passed is already wider than the range (such as u64), translation is refused; a value of a type that can fit, such as u8, is checked at the call. Values that come from outside the program (here, --run’s arguments) are also checked at the boundary, and 101 stops there. The specification says translation is refused when the caller cannot prove the range, but the compiler in this edition hands unproven u8 values to a run-time check.
Who measures the range, and where, in one picture (the literal 101 was measured on this edition).
value the caller passes before entering scale
42 (literal) ── measured at translation ─▶ passes
101 (literal) ── measured at translation ─▶ E-TYPE-RANGE (refused)
a u8 value ── checked at the call ─────▶ stops if 101
a --run argument ── checked at the boundary ─▶ stops if 101
a u64 value ── already wider ───────────▶ E-TYPE-WIDTH (refused)The same could be written as requires le a 100 .. The difference is where it is written. range becomes the shape of the parameter, so the caller sees it from the signature alone, and it can be carried into several ops through an alias (type pct range 0 100 .).
13.3 cast — where a value may change#
widen is only for places where the value does not change. Conversions that change sign or cross kinds (floating point ↔ integer) are written cast <type> <value>.
examples/ch13/casts.low
module casts .
rem run: truncate 7.9
rem run: truncate -2.9
rem run: to_signed 200
rem trap: to_signed 3000000000
fn truncate input x f64 . output i32 .
do
return cast i32 x .
end
fn to_signed input x u32 . output i32 .
do
return cast i32 x .
end
Output
$ lowentc --run truncate casts.low 7.9
truncate(7.9) = 7
$ lowentc --run truncate casts.low -2.9
truncate(-2.9) = -2
$ lowentc --run to_signed casts.low 200
to_signed(200) = 200
$ lowentc --run to_signed casts.low 3000000000
== ir diagnostics (1) ==
0:0 E-VM-CAST: value does not fit the target width (use narrow_wrap / narrow_sat / narrow_try)
- Converting floating point to integer truncates towards zero: 7.9 becomes 7, −2.9 becomes −2. Discarding the fraction does not stop.
- If the integer part or the value does not fit the target type, execution stops. Three billion does not fit in an
i32. - A
boolcan be neither the target nor the source of acast. To go between booleans and numbers, write anif.
cast is not a mark meaning “anything goes”. It is a mark meaning I know the value may change here. It does not break the principle that no value-losing conversion happens implicitly (chapter 4), because the author wrote the word.
A common misconception. cast moves the bits unchanged, like a C cast
(int32_t)x truncates or wraps values that do not fit, depending on the implementation. Lowent’s cast moves the value and stops when it does not fit. Keeping the bits and changing only how they are read is a separate job, done by bit_cast (chapter 20). Two jobs do not share one word.13.4 bits — from 1 to 64 bits#
type <name> bits <count> . makes an integer type with that many bits. The width need not be a power of two.
examples/ch13/tiny.low
module tiny .
rem run: grow 1000
rem trap: grow 1023
type ten_bits bits 10 .
fn grow input x ten_bits . output ten_bits .
do
return add x 1 .
end
Output
$ lowentc --run grow tiny.low 1000
grow(1000) = 1001
$ lowentc --run grow tiny.low 1023
== ir diagnostics (1) ==
0:0 E-VM-OVERFLOW: integer overflow at the declared width (use wrap_*/sat_*, or prove the range)
ten_bits is 0 … 1023. Adding 1 to 1023 overflows ten bits and stops. When dealing with protocol bit fields or packed tables, the width becomes the contract. But when the width does not match the machine’s word, there is no promise of speed. What the language promises is the correct answer computed in ten bits.
13.5 Pinning down byte layout#
A struct’s layout is normally chosen by the processor to suit the machine. When bytes must mean the same outside — a header going onto the wire, a register a device reads — write the layout.
examples/ch13/packed.low
module packed .
type bytes slice u8 .
struct wire_header do
layout packed .
magic u32 big .
length u16 big .
kind u8 .
end
fn header_kind input b bytes . output u64 .
do
let v view wire_header be view wire_header b .
return widen u64 (field v kind) .
end
Output
$ lowentc --check packed.low
== check: ok ==
layout packed .puts no padding between fields. Fields sit next to each other in declaration order.bigandlittleafter a field set its byte order. If not written, the machine’s order is used.view wire_header breads a byte slice as a value of that layout without copying. If length or alignment do not fit, execution stops.
A header holding magic 1 · length 2 · kind 9 lies in bytes like this. big puts the high byte first.
byte: 0 1 2 3 4 5 6
┌────┬────┬────┬────┬────┬────┬────┐
│ 00 │ 00 │ 00 │ 01 │ 00 │ 02 │ 09 │
└────┴────┴────┴────┴────┴────┴────┘
└───── magic ─────┘ └─ length ┘ kind
u32 big u16 big u8A field can also carry an access mark such as rw, ro or wo. In a struct that maps device registers, reading a write-only field is refused at translation (chapter 30). Pinning the layout takes choices away from the processor, so pin it only where needed.
13.6 Both directions of a layout, and a view that can fail#
view reads bytes as a value of that layout. The opposite direction — making bytes of that layout from a value — is encode.
examples/ch13/encoded.low
module encoded .
rem run: header_bytes 9
struct wire_header do
layout packed .
magic u32 big .
length u16 big .
kind u8 .
end
rem the opposite of `view` --- turn a value into bytes of that layout
fn header_bytes input k u8 . output u64 .
do
let h wire_header be make wire_header do magic 1 . length 2 . kind k . end .
let bytes slice u8 be encode wire_header h .
return add (mul (len bytes) 1000) (widen u64 (index bytes 6)) .
end
Output
$ lowentc --run header_bytes encoded.low 9
header_bytes(9) = 7009
encode wire_header h produces seven bytes in the byte order written on each field (big). The result 7009 puts the length 7 and the 9 of the last byte, kind, side by side. The side that builds a header going onto the wire uses encode; the side that reads a received header uses view.
bytes 00 00 00 01 00 02 09 ── view · try_view ──▶ a wire_header value
(wire · file · device) ◀──── encode ───────── magic 1 · length 2 · kind 9view stops when the length or alignment is off, because it treats that as a broken contract. Bytes from a network, however, are often short. In such places use try_view, which does the same work but reports failure as a value (chapter 17).
examples/ch13/tryview.low
module tryview .
rem run: kind_or_zero [1,2,3,4,0,5,9]
rem run: kind_or_zero [1,2,3]
struct wire_header do
layout packed .
magic u32 big .
length u16 big .
kind u8 .
end
rem bytes from outside may be short --- `try_view` gives `none` instead of stopping like `view`
fn kind_or_zero input b slice u8 . output u64 .
do
let o option view wire_header be try_view wire_header b .
guard is_some o . else return 0 .
let v view wire_header be some_value o .
return widen u64 (field v kind) .
end
Output
$ lowentc --run kind_or_zero tryview.low [1,2,3,4,0,5,9]
kind_or_zero([1,2,3,4,0,5,9]) = 9
arg0 (written) = [1,2,3,4,0,5,9]
$ lowentc --run kind_or_zero tryview.low [1,2,3]
kind_or_zero([1,2,3]) = 0
arg0 (written) = [1,2,3]
With seven bytes it reads kind 9; with three it gets none and returns 0. Use the stopping view inside, where things are already checked, and the value-reporting try_view at the boundary.
13.7 bitset — a set of small numbers#
bitset <bits> is a set recording whether each number from 0 up to (not including) that count is present. Its name resembles bits (the width of one integer), but the meaning differs: bitset is not a tool for the bits of a word but a set, and the bits of a word are handled by bit operations such as bit_and and shl.
examples/ch13/sets.low
module sets .
rem run: overlap 5
rem run: overlap 3
rem run: flipped
rem a collection of which numbers in 0 … 63 are present --- a set, not the bits of a word
fn overlap input n u64 . output u64 .
requires le n 60 .
do
var a bitset 64 be bitset_new 64 .
var b bitset 64 be bitset_new 64 .
add a 1 .
add a 3 .
add a 5 .
add b 3 .
add b n .
let both bitset 64 be intersect a b .
let only_a bitset 64 be difference a b .
rem remove 1 from the set --- it changes in place
remove a 1 .
var code u64 be add (mul (count both) 100) (mul (count only_a) 10) .
if is_subset both a . do set code (add code 1) . end
if is_empty only_a . do set code (add code 1000) . end
return code .
end
rem a complement only means something within the width --- put one of eight in, and seven remain
fn flipped output u64 .
do
var a bitset 8 be bitset_new 8 .
add a 0 .
return count (complement a) .
end
Output
$ lowentc --run overlap sets.low 5
overlap(5) = 211
$ lowentc --run overlap sets.low 3
overlap(3) = 121
$ lowentc --run flipped sets.low
flipped() = 7
bitset_new 64makes an empty set. The width is a number fixed at translation time.add a 1 .inserts andremove a 1 .removes. Both are statements that change the set in place.count ais the number of members.intersect a bgives the intersection,difference a bwhat is only ina, andcomplement athe complement, each as a new set.is_subset x yasks whether all ofxis iny;is_empty xasks whether it is empty.
In overlap 5 the intersection is {3, 5} and what is only in a is {1}, giving 211. complement only means something within the width: put 0 into an eight-slot set and its complement is the other seven. Inserting or asking about a number outside the width stops the program — the range of the set is part of its type too.
13.8 Scattered pieces as one view — segments#
Sometimes bytes do not lie in one row but are scattered over several pieces, like pieces received from the network or the two ends of a ring buffer. Gathering them into one place means copying. segments views the pieces as one without copying.
examples/ch13/pieces.low
module pieces .
rem run: main
proc main input al cap allocator . output u8 . effects alloc .
do
let mb option mut slice u8 . . be alloc_bytes al capacity 16 .
guard is_some mb . else return 250 .
let md option mut slice u8 . . be alloc_bytes al capacity 32 .
guard is_some md . else return 251 .
rem the 16 backing bytes
var back mut slice u8 . be some_value mb .
set (index back 4) 20 .
set (index back 9) 30 .
rem the descriptor: two (start, length) pairs, 4 from 0 and 4 from 8
var d mut slice u64 . be view_array u64 (some_value md) .
set (index d 0) 0 .
set (index d 1) 4 .
set (index d 2) 8 .
set (index d 3) 4 .
rem see the two pieces as one view, without copying
let ss segments u8 be view_segments back d .
let p slice u8 be seg ss 1 .
rem the piece count 2 times 10, plus byte 1 of the second piece (the 30 at original position 9)
return narrow u8 (add (mul (segs ss) 10) (index p 1)) .
end
Output
$ lowentc --run main pieces.low
main() = 50
view_segments back dbinds the backing bytesbackand the descriptordinto a view of typesegments u8. The descriptor is a row of (start, length) pairs; here 4 bytes from 0 and 4 bytes from 8.segs ssgives the piece count 2, andseg ss 1gives the second piece as an ordinaryslice u8. Byte 1 of the second piece is the 30 at original position 9.- There is no new machine instruction. It lowers to building a grouping, reading fields and slicing, so the cost is visible.
13.9 Three kinds of type words#
The words that may stand in a type position form a closed list set by the canon. Some of them are known by name only and have no meaning yet.
examples/ch13/notyet.low
module notyet .
rem expect: W-NOT-YET
rem byte is a type word known by name only; it has no meaning yet, so a warning is given; write a byte as u8
fn first input x byte . output u64 . do
return 0 .
end
rem `list` in grammar annex A.9 is the same — it was on the list, but no clause ever gave it a meaning
fn count input xs list u64 . output u64 .
do
return 0 .
end
Output
$ lowentc --check notyet.low
notyet.low:10:0 E-NAME-BUILTIN: this name is a BUILTIN — the resolver always picks the builtin, so your declaration can never be called: it exists and does not exist. The namespace is FLAT (no shadowing). Rename it
notyet.low:5:0 W-NOT-YET: this type NAME is accepted but carries NO MEANING yet: lowering cannot read it, so every op in its signature falls to the interpreter (~80x). The answer stays right, so no oracle will ever see this. Declare it (`type str slice u8 .`) or write the underlying type
notyet.low:10:0 W-NOT-YET: this type NAME is accepted but carries NO MEANING yet: lowering cannot read it, so every op in its signature falls to the interpreter (~80x). The answer stays right, so no oracle will ever see this. Declare it (`type str slice u8 .`) or write the underlying type
byte is accepted, but W-NOT-YET says “no meaning”. Accepting it silently would make the writer think it works. Write a byte as u8.
| Kind | Words | What the tool does |
|---|---|---|
| usable | numbers · bool · void · slice · array · segments · set · stack · range · vec · bitset · mask · result · option · ref · mut_ref · mut · owned · region · cap · self | checks and lowers them by meaning |
| known by name | byte · char · str · string · bytes_view · dyn · atomic | W-NOT-YET — says there is no meaning |
| refused for now | shared_read · lock · rwlock | E-LOCK-NOTYET — state shared between flows (chapter 26) |
| qualifier | unsafe_ptr | a C pointer mark placed before a type (chapter 29) |
| no meaning in the canon | list · raw · addr · rng | this edition’s tool accepts them without a warning — do not use them |
Table 13.1 — Type words — usable · known by name · refused
The last row is a hole in this edition. The four words are in the canon’s list, but no clause gives them a meaning, and the tool accepts them without a word. It is recorded as a defect in the development repository.
13.10 Common mistakes#
Counter-example. Passing a value of a wider type straight to a range parameter
examples/ch13/mistake_rangewide.low
module mistake_rangewide .
rem expect: E-TYPE-WIDTH
fn scale input a range 0 100 . output u8 .
do
return narrow u8 (mul a 2) .
end
fn call_scale input x u64 . output u8 .
do
rem ✘ `u64` is wider than 0 … 100 --- the caller has not shown the value is in range
return scale x .
end
Output
$ lowentc --check mistake_rangewide.low
mistake_rangewide.low:12:0 E-TYPE-WIDTH: value does not fit the declared type (a literal out of range, or a narrowing — use narrow / narrow_wrap / narrow_sat / round_to)
scale says in its signature that it takes only 0 … 100. A u64 value is wider than that, so unless the caller shows it is in range, translation rejects the call with E-TYPE-WIDTH. The point of rejecting is to settle who is responsible: only the caller knows what to do with an out-of-range value. Check the range with guard, answer separately when it is outside, and pass the value on with narrow when it is inside.
examples/ch13/rangewide_fixed.low
module rangewide_fixed .
rem run: call_scale 40
rem run: call_scale 400
fn scale input a range 0 100 . output u8 .
do
return narrow u8 (mul a 2) .
end
fn call_scale input x u64 . output u8 .
do
rem answer separately when out of range; otherwise narrow and pass it on
guard le x 100 . else return 255 .
return scale (narrow u8 x) .
end
Output
$ lowentc --run call_scale rangewide_fixed.low 40
call_scale(40) = 80
$ lowentc --run call_scale rangewide_fixed.low 400
call_scale(400) = 255
Counter-example. Using widen to put a signed number into an unsigned type
examples/ch13/mistake_widensign.low
module mistake_widensign .
rem expect: E-WIDEN-SIGN
fn to_count input x i32 . output u64 .
do
rem ✘ a negative number has no place in `u64` --- `widen` fails even though the width is larger
return widen u64 x .
end
Output
$ lowentc --check mistake_widensign.low
mistake_widensign.low:7:0 E-WIDEN-SIGN: a signed integer cannot widen into an unsigned one — a negative value has no place there. Use `cast` if that is what you mean
Going from i32 to u64 makes the width larger, but a negative number such as −1 has no place in u64. widen is only for places where no value changes, so this is E-WIDEN-SIGN. If you know no negative value can arrive, write cast u64 x to leave that judgement in the source; cast stops if a negative value does arrive. If negatives need their own handling, put guard ge x 0 . first.
Counter-example. Turning a bool into a number with cast
examples/ch13/mistake_boolcast.low
module mistake_boolcast .
rem expect: E-TYPE-KIND
fn flag input b bool . output u8 .
do
rem ✘ whether true is 1 is for the program to decide, not the language
return cast u8 b .
end
Output
$ lowentc --check mistake_boolcast.low
mistake_boolcast.low:7:0 E-TYPE-KIND: bool is not numeric: it cannot be a cast SOURCE either. Which side is `1` is for the PROGRAM to say, not the language — write `if b . do 1 . else 0 .`
C treats true as 1, but whether true is 1, 0 or −1 is a promise of the program, not something the language should decide. So bool can be neither the source nor the target of cast, and it is rejected with E-TYPE-KIND. As the tool suggests, write “1 if true, 0 if false” with if. That one line puts the promise in the source.
A common misconception. Giving a type another name with type keeps units from mixing
examples/ch13/type_units.low
module type_units .
rem run: total 100 100
type meters u64 .
type feet u64 .
rem meters and feet are added and nothing stops it --- `type` is only another name
fn total input m meters . input f feet . output meters .
requires le m 1000000 .
requires le f 1000000 .
do
return add m f .
end
Output
$ lowentc --run total type_units.low 100 100
total(100, 100) = 200
meters and feet are both just other names for u64, so nothing stops you adding them: 100 meters plus 100 feet comes out as 200 meters. Meanings that must not mix are separated with newtype; then, as in ids.low, translation stops the mix with E-TYPE-NOMINAL, and every crossing shows up as a cast. Use type to shorten long names or to hand down a range.
A common misconception. cast of a negative number into an unsigned type gives a big number, as in C
examples/ch13/negcast.low
module negcast .
rem run: to_unsigned 5
rem trap: to_unsigned -1
fn to_unsigned input x i32 . output u32 .
do
rem in C, -1 would become 4294967295 --- Lowent stops when the value does not fit
return cast u32 x .
end
Output
$ lowentc --run to_unsigned negcast.low 5
to_unsigned(5) = 5
$ lowentc --run to_unsigned negcast.low -1
== ir diagnostics (1) ==
0:0 E-VM-CAST: value does not fit the target width (use narrow_wrap / narrow_sat / narrow_try)
In C, (uint32_t)-1 is 4294967295. That wrap-around is defined by the standard, but it usually hides a bug. Lowent’s cast moves the value and stops with E-VM-CAST when it does not fit. If you really want wrapping, use a word that carries that meaning in its name, such as narrow_wrap (chapter 4).
13.11 This chapter’s syntax at a glance#
| Shape | Meaning | Why |
|---|---|---|
type meters u64 . | another name for the same type | shortens a type and records meaning — does not stop mixing |
newtype user_id u64 . | a new type with the same representation | translation stops ids from mixing — no run-time cost |
cast user_id n · cast u64 u | cross between the new type and the original | crossings gather in one place in the code |
input a range 0 100 . | accept only values in the range, both ends included | the contract becomes the shape of the signature |
type pct range 0 100 . | put a range on an alias | many ops inherit the same range |
cast i32 x | a conversion that may change the value — stops if it does not fit | a mark that says “I know the value may change here” |
type ten_bits bits 10 . | an integer of 1 … 64 bits | the width is the contract |
layout packed . · magic u32 big . | layout without padding · byte order | make bytes mean the same outside |
view wire_header b | read bytes in that layout without copying | stops if length or alignment is off |
try_view wire_header b · encode wire_header h | a view that gives none on failure · a value into bytes of that layout | at a boundary, the non-stopping one |
var a bitset 64 be bitset_new 64 . · add a 1 . · intersect a b | a set of small numbers and its operations | a set, not the bits of a word |
view_segments back d · segs ss · seg ss i | scattered pieces as one view without copying · piece count · piece i | removes the gathering copy |
type words such as byte · lock | W-NOT-YET · E-LOCK-NOTYET | if there is no meaning, the tool says so |
Table 13.2 — Named type syntax — shape · meaning · why it looks this way
Recap
type is an alias and newtype a new type with the same representation; cast crosses between them. range lo hi is a contract that has become the shape of a parameter: the caller keeps it and the body uses it as a fact. cast is the word for conversions that may change the value and stops when the value does not fit. bits N is an arbitrary-width integer. layout packed, byte-order marks and view match byte layout with the outside world.