#set document(title: "6.2 Unsafe pointers", author: "Modular Inc. / XYZ Homework") #set page(width: 8.5in, height: auto, margin: 1in) #import "@preview/cetz:0.5.2" #set text(font: ("STIX Two Text", "Libertinus Serif", "New Computer Modern"), size: 10.5pt, lang: "en") #show math.equation: set text(font: ("STIX Two Math", "New Computer Modern Math")) #set par(justify: true, leading: 0.62em, spacing: 0.9em) #set enum(spacing: 1.1em) // room between list items so tall inline fractions don't collide #set list(spacing: 1.1em) #set table(stroke: 0.5pt + rgb("#c7ccd3")) #let BLUE = rgb("#183B6F") // brand navy — section bars + example/solution labels (white on navy 11.09:1) #let ORANGE = rgb("#A94509") // brand primary-700 — AA-safe deep orange for TEXT (5.93:1 on white; raw brand #F37021 is 2.94:1 and must never carry text) #let RED = rgb("#DC2626") // brand error-600 #let GREEN = rgb("#059669") // brand success-600 (decoration only; small green text uses green-text #007942) #show heading.where(level: 1): it => block(width: 100%, above: 0pt, below: 16pt, fill: gradient.linear(BLUE, rgb("#2C5AA0")), inset: (x: 14pt, y: 12pt), radius: 3pt, text(fill: white, weight: "bold", size: 19pt, it.body)) #show heading.where(level: 2): it => block(width: 100%, above: 18pt, below: 10pt, fill: BLUE, inset: (x: 10pt, y: 6pt), radius: 2pt, text(fill: white, weight: "bold", size: 12pt, it.body)) #show heading.where(level: 3): it => text(fill: ORANGE, weight: "bold", size: 12.5pt, it.body) #show heading.where(level: 4): it => text(fill: BLUE, weight: "bold", size: 10.5pt, it.body) #let examplebox(label, title, body) = block(width: 100%, breakable: true, fill: rgb("#EFF1F5"), stroke: 0.5pt + rgb("#CFDDF0"), radius: 4pt, inset: 10pt, above: 12pt, below: 12pt)[ #block(below: 6pt)[#box(fill: BLUE, inset: (x: 6pt, y: 2pt), radius: 2pt, text(fill: white, weight: "bold", size: 8.5pt, label)) #h(0.4em) #strong[#title]] #body] // rail = decorative left rule (raw brand token); labelcolor = AA-safe label text shade #let notebox(label, rail, labelcolor, tint, body) = block(width: 100%, breakable: true, fill: tint, stroke: (left: 3pt + rail), inset: (left: 10pt, rest: 8pt), radius: (right: 4pt), above: 11pt, below: 11pt)[ #text(fill: labelcolor, weight: "bold", size: 7.5pt, tracking: 0.5pt)[#upper(label)] #linebreak() #body] #let solutionbox(body) = block(above: 4pt, below: 8pt)[ #text(fill: BLUE, weight: "bold", size: 8.5pt)[Solution] #linebreak() #body] #let figph(msg) = block(width: 100%, height: 60pt, fill: rgb("#f6f7f9"), stroke: (paint: rgb("#c7ccd3"), dash: "dashed"), radius: 4pt, inset: 10pt)[ #align(center + horizon, text(fill: rgb("#889"), style: "italic", size: 9pt, msg))] // Standardize inlined figure sizes: measure the natural CeTZ canvas, then scale to a // consistent envelope (aspect-aware; see build_typst.py FIG_* constants). Unlike the // print preamble, dimensions are FLOORED: in an editor a user can trim a figure to a // degenerate 1-D shape (a bare line), and w/h or tw/w would then divide by zero. #let _STD_W = 3.5 #let _WIDE_W = 5.6 #let _MAX_H = 3.4 #let _ASPECT_WIDE = 2.2 #let _UPSCALE_MAX = 1.15 #let stdfig(body) = context { let m = measure(body) let w = calc.max(m.width / 1in, 0.01) let h = calc.max(m.height / 1in, 0.01) let tw = if w / h > _ASPECT_WIDE { _WIDE_W } else { _STD_W } let s = calc.min(tw / w, _MAX_H / h, _UPSCALE_MAX) align(center, box(scale(x: s * 100%, y: s * 100%, reflow: true, body))) } #show figure: set block(breakable: false) #set figure(gap: 8pt) #show figure.caption: set text(size: 8.5pt, fill: rgb("#555")) == 6.2#h(0.6em)Unsafe pointers The #link("https://mojolang.org/docs/std/memory/unsafe_pointer/UnsafePointer/")[UnsafePointer] type is one of several pointer types available in the standard library to indirectly reference locations in memory. You can use an UnsafePointer to dynamically allocate and free memory, or to point to memory allocated by some other piece of code. You can use these pointers to write code that interacts with low-level interfaces, to interface with other programming languages, or to build array-like data structures. But as the name suggests, they're inherently #emph[unsafe]. For example, when using unsafe pointers, you're responsible for ensuring that memory gets allocated and freed correctly. In general, you should prefer safe pointer types when possible, reserving UnsafePointer for those use cases where no other pointer type works. For a comparison of standard library pointer types, see Intro to pointers. === Unsafe pointer basics An UnsafePointer is a type that holds an address to memory. You can store and retrieve values in that memory. The UnsafePointer type is #emph[generic]—it can point to any type of value, and the value type is specified as a parameter. The value pointed to by a pointer is sometimes called a #emph[pointee]. \# Allocate memory to hold a value var ptr = alloc\[Int\](1) \# Initialize the allocated memory ptr.init\_pointee\_copy(100)#figure(figph[Diagram: images pointer diagram], alt: "Diagram: images pointer diagram", caption: none) Accessing the memory—to retrieve or update a value—is called #emph[dereferencing] the pointer. You can dereference a pointer by following the variable name with an empty pair of square brackets: \# Update an initialized value ptr\[\] += 10 \# Access an initialized value print(ptr\[\])110You can also allocate memory to hold multiple values to build array-like structures. For details, see Storing multiple values. === Lifecycle of a pointer At any given time, a pointer can be in one of several states. An UnsafePointer is non-nullable. It can be #emph[uninitialized], #emph[dangling], or point to a memory location: - Uninitialized. Just like any variable, a variable of type UnsafePointer can be declared but uninitialized. var ptr: UnsafePointer\[Int, MutUntrackedOrigin\] - Pointing to allocated, uninitialized memory. The alloc() function returns a pointer to a newly-allocated block of memory with space for the specified number of elements of the pointee's type. ptr = alloc\[Int\](1)Trying to dereference a pointer to uninitialized memory results in undefined behavior. - Pointing to initialized memory. You can initialize an allocated, uninitialized pointer by moving or copying an existing value into the memory. Or you can get a pointer to an existing value by calling the constructor with the to keyword argument. ptr.init\_pointee\_copy(value) \# or ptr.init\_pointee\_move(value^) \# or ptr = UnsafePointer(to=value)Once the value is initialized, you can read or mutate it using the dereference syntax: var oldValue = ptr\[\] ptr\[\] = newValue - Dangling. When you free the pointer's allocated memory, you're left with a #emph[dangling pointer]. The address still points to its previous location, but the memory is no longer allocated to this pointer. Trying to dereference the pointer, or calling any method that would access the memory location results in undefined behavior. ptr.free() The following diagram shows the lifecycle of an UnsafePointer: #figure(figph[Diagram: images pointer lifecycle], alt: "Diagram: images pointer lifecycle", caption: none) ==== Allocating memory Use the alloc() function to allocate memory. This function returns a new pointer pointing to the requested memory. You can allocate space for one or more values of the pointee's type. Allocation failure traps the program. The alloc() function always returns a valid, non-null pointer: var ptr = alloc\[Int\](10) \# Allocate space for 10 Int valuesThe allocated space is #emph[uninitialized]—like a variable that's been declared but not initialized. ==== Initializing the pointee To initialize allocated memory, UnsafePointer provides the #link("https://mojolang.org/docs/std/memory/unsafe_pointer/UnsafePointer/#init_pointee_copy")[init\_pointee\_copy()] and #link("https://mojolang.org/docs/std/memory/unsafe_pointer/UnsafePointer/#init_pointee_move")[init\_pointee\_move()] methods. For example: ptr.init\_pointee\_copy(my\_value)To move a value into the pointer's memory location, use init\_pointee\_move(): str\_ptr.init\_pointee\_move(my\_string^)Note that to move the value, you usually need to add the transfer sigil (^), unless the value is a trivial type (like Int) or a newly-constructed, "owned" value: str\_ptr.init\_pointee\_move("Owned string")Alternately, you can get a pointer to an existing value by calling the UnsafePointer constructor with the keyword to argument. This is useful for getting a pointer to a value on the stack, for example. var counter: Int = 5 var ptr = UnsafePointer(to=counter)Note that when calling UnsafePointer(to=value), you don't need to allocate memory, since you're pointing to an existing value. ==== Dereferencing pointers Use the \[\] dereference operator to access the value stored at a pointer (the "pointee"). \# Read from pointee print(ptr\[\]) \# mutate pointee ptr\[\] = 05If you've allocated space for multiple values, you can use subscript syntax to access the values, as if they were an array, like ptr\[3\]. The empty subscript \[\] has the same meaning as \[0\]. #notebox("Note", rgb("#8a94a6"), rgb("#556666"), rgb("#f7f8fa"))[ The dereference operator assumes that the memory being dereferenced is initialized. Dereferencing uninitialized memory results in undefined behavior. ] You cannot safely use the dereference operator on uninitialized memory, even to #emph[initialize] a pointee. This is because assigning to a dereferenced pointer calls lifecycle methods on the existing pointee (such as the destructor, move constructor or copy constructor). var str\_ptr = alloc\[String\](1) \# str\_ptr\[\] = "Testing" \# Undefined behavior! str\_ptr.init\_pointee\_move("Testing") str\_ptr\[\] += " pointers" \# Works now ==== Destroying or removing values The #link("https://mojolang.org/docs/std/memory/unsafe_pointer/UnsafePointer/#take_pointee")[take\_pointee()] method moves a pointee from the memory location pointed to by ptr. This is a consuming move. It invokes \_\_init\_\_(take=) on the destination value. It leaves the memory location uninitialized. The #link("https://mojolang.org/docs/std/memory/unsafe_pointer/UnsafePointer/#destroy_pointee")[destroy\_pointee()] method calls the destructor on the pointee, and leaves the memory location pointed to by ptr uninitialized. Both take\_pointee() and destroy\_pointee() require that the pointer is non-null, and the memory location contains a valid, initialized value of the pointee's type; otherwise the function results in undefined behavior. Calling #link("https://mojolang.org/docs/std/memory/unsafe_pointer/UnsafePointer/#init_pointee_move_from")[init\_pointee\_move\_from(self, src)] moves the value pointed to by src into the memory location pointed to by self. After this operation, ownership of that value transfers from src to self and the memory at src is uninitialized: do not read from it, and do not invoke destructors on it. To make the memory valid again, initialize it with a new value using one of the init\_pointee\_\* operations. #notebox("Note", rgb("#8a94a6"), rgb("#556666"), rgb("#f7f8fa"))[ Mojo assumes the destination memory is uninitialized. It does not destroy existing contents before writing the value from src. ] ==== Freeing memory Calling #link("https://mojolang.org/docs/std/memory/unsafe_pointer/UnsafePointer/#free")[free()] on a pointer frees the memory allocated by the pointer. It doesn't call the destructors on any values stored in the memory—you need to do that explicitly (for example, using #link("https://mojolang.org/docs/std/memory/unsafe_pointer/UnsafePointer/#destroy_pointee")[destroy\_pointee()] or one of the other functions described in Destroying or removing values). Disposing of a pointer without freeing the associated memory can result in a memory leak—where your program keeps taking more and more memory, because not all allocated memory is being freed. On the other hand, if you have multiple copies of a pointer accessing the same memory, you need to make sure you only call free() on one of them. Freeing the same memory twice is also an error. After freeing a pointer's memory, you're left with a dangling pointer—its address still points to the freed memory. Any attempt to access the memory, like dereferencing the pointer results in undefined behavior. === Storing multiple values As mentioned in Allocating memory, you can use an UnsafePointer to allocate memory for multiple values. The memory is allocated as a single, contiguous block. Pointers support arithmetic: adding an integer to a pointer returns a new pointer offset by the specified number of values from the original pointer: var third\_ptr = first\_ptr + 2Pointers also support subtraction, as well as in-place addition and subtraction: \# Advance the pointer one element: ptr += 1#figure(figph[Diagram: images pointer offset], alt: "Diagram: images pointer offset", caption: none) For example, the following example allocates memory to store 6 Float64 values, and initializes them all to zero. var float\_ptr = alloc\[Float64\](6) for offset in range(6): (float\_ptr+offset).init\_pointee\_copy(0.0)Once the values are initialized, you can access them using subscript syntax: float\_ptr\[2\] = 3.0 for offset in range(6): print(float\_ptr\[offset\], end=", ")0.0, 0.0, 3.0, 0.0, 0.0, 0.0, === UnsafePointer and origins The UnsafePointer struct has an origin parameter to track the origin of the memory it points to. The full parameter signature for UnsafePointer looks like this: struct UnsafePointer\[ mut: Bool, //, type: AnyType, origin: Origin\[mut=mut\], \*, address\_space: AddressSpace = AddressSpace.GENERIC, \]For pointers initialized with the to keyword argument, the origin is inferred from the origin of the pointee. For example, in the following code, s\_ptr.origin is the same as the origin of s: s = "Testing" s\_ptr = UnsafePointer(to=s)When allocating memory with the alloc() function, the returned pointer has an origin value of MutUntrackedOrigin. This value represents an origin that is mutable and doesn't #emph[alias] existing values. For example, it doesn't point to the memory allocated for any other variable. This memory isn't tracked by Mojo's lifetime checker and you're responsible for freeing it. If you're using a pointer in the implementation of a struct, you usually don't have to worry about the origin, as long as the pointer isn't exposed outside of of the struct. For example, if you implement a static array type that allocates memory in its constructor, deallocates it in its destructor, and doesn't expose the pointer outside of the struct, the default origin is fine. But if the struct exposes a pointer or reference to that memory, you need to set the origin appropriately. For example, the List type has an unsafe\_ptr() method that returns an UnsafePointer to the underlying storage. In this case, the returned pointer should share the origin of the list, since the list is the logical owner of the storage. That method looks something like this: def unsafe\_ptr( ref self, ) -\> UnsafePointer\[T, origin\_of(self)\]: return self.data.unsafe\_origin\_cast\[origin\_of(self)\]()This returns a copy of the original pointer, with the origin set to match the origin and mutability of the self value. A method like this is unsafe, but setting the correct origin makes it safer, since the compiler knows that the pointer is referring to data owned by the list. When taking a pointer as a function argument, you often want to require either a mutable or immutable origin, but otherwise allow the compiler to infer the origin. Here's an example: def print\_bytes(bytes: UnsafePointer\[mut=False, Byte, \_\], count: Int): for i in range(count): print(hex(bytes\[i\]), end=" ") print() By binding the infer-only \`mut\` parameter to \`False\`, and leaving the origin unbound (using \`\_\`), this signature lets the compiler infer the origin, but forces the origin to be immutable. Mojo can implicitly cast a mutable pointer to an immutable pointer, so you can pass a mutable pointer into \`print\_bytes()\`, but the function can't mutate the data. \#\# Working with nullability \`UnsafePointer\` is a non-nullable type. To model a null pointer, wrap it in \`Optional\`: \`\`\`mojo var ptr = Optional\[UnsafePointer\[Int, MutUntrackedOrigin\]\]()This creates an Optional with a value of None, which is equivalent to a null pointer. Optional\[UnsafePointer\] has the same memory layout as a raw pointer, so you can pass it across FFI boundaries as NULL. To check whether an optional pointer is null, use Optional methods: if ptr: \# ptr is not None — safe to unwrap var p = ptr.value()When you need a non-null value for deferred initialization, use unsafe\_dangling() instead: var ptr = UnsafePointer\[Int, MutUntrackedOrigin\].unsafe\_dangling()For a practical example of optional pointers in a data structure, see Self-referential structs. === Working with foreign pointers When exchanging data with other programming languages, you may need to construct an UnsafePointer from a foreign pointer. Mojo restricts creating UnsafePointer instances from arbitrary addresses, to avoid users accidentally creating pointers that #emph[alias] each other (that is, two pointers that refer to the same location). However, there are specific methods you can use to get an UnsafePointer from a Python or C/C++ pointer. When dealing with memory allocated elsewhere, you need to be aware of who's responsible for freeing the memory. Freeing memory allocated elsewhere can result in undefined behavior. When working with some foreign functions, you may need to supply a pointer with no specific type (a type-erased pointer, or "void pointer" in C/C++). This is equivalent to a Mojo OpaquePointer. You also need to be aware of the format of the data stored in memory, including data types and byte order. For more information, see Converting data: bitcasting and byte order. ==== Creating a Mojo pointer from a raw memory address You can create a UnsafePointer from a raw memory address using the unsafe\_from\_address initializer. def write\_to\_address(mmio\_address: Int, value: Int32): var ptr = UnsafePointer\[Int32, MutUntrackedOrigin\]( unsafe\_from\_address=mmio\_address ) \# Writing to a raw memory address may require a volatile load/store as the \# operation may have side effects not visible to the compiler. \# You can specify this using the \`volatile\` parameter. ptr.store\[volatile = True\](value)This is unsafe, as the caller must ensure the address is valid before writing to it, and that the memory is initialized before reading from it. The caller must also ensure the pointer's origin and mutability is valid for the address, failure to do so may result in undefined behavior. ==== Creating a Mojo pointer from a Python pointer The PythonObject type defines an #link("https://mojolang.org/docs/std/python/python_object/PythonObject/#unsafe_get_as_pointer")[unsafe\_get\_as\_pointer()] method to construct an UnsafePointer from a Python address. For example, the following code creates a NumPy array and then accesses the data using a Mojo pointer: from std.python import Python def share\_array() raises: var np = Python.import\_module("numpy") var arr = np.array(Python.list(1, 2, 3, 4, 5, 6, 7, 8, 9)) var ptr = arr.ctypes.data.unsafe\_get\_as\_pointer\[DType.int64\]() for i in range(9): print(ptr\[i\], end=", ") print() def main() raises: share\_array()1, 2, 3, 4, 5, 6, 7, 8, 9,This example uses the NumPy #link("https://numpy.org/doc/stable/reference/generated/numpy.ndarray.ctypes.html#numpy.ndarray.ctypes")[ndarray.ctype] attribute to access the raw pointer to the underlying storage (ndarray.ctype.data). The unsafe\_get\_as\_pointer() method constructs an UnsafePointer to this address. ==== Working with C/C++ pointers If you call a C/C++ function that returns a pointer using the #link("https://mojolang.org/docs/std/ffi/external_call/")[external\_call] function, you can specify the return type as an UnsafePointer, and Mojo will handle the type conversion for you. Notably, the origin parameter when working across FFI boundaries should often be set to (Mut/Immut)UntrackedOrigin, since the pointer points to memory allocated outside of the Mojo program. from std.ffi import external\_call def get\_foreign\_pointer() -\> UnsafePointer\[Int, MutUntrackedOrigin\]: var ptr = external\_call\[ "my\_c\_function", \# external function name UnsafePointer\[Int, MutUntrackedOrigin\] \# return type \]() return ptr ==== Opaque pointers The OpaquePointer type is a pointer that does not have a specific type. In other languages, this is usually called a type-erased pointer or a void pointer. Opaque pointers are usually used when interfacing with non-Mojo code, such as a C library function that takes a void pointer. OpaquePointer is actually a type alias for UnsafePointer\[NoneType\], so it has the same API as any other UnsafePointer. You can't dereference an opaque pointer, but you can cast it to a specific type using the bitcast() method. Similarly, you can create an opaque pointer from an existing pointer by bitcasting to NoneType. For example: var str = "Hello, world!" var str\_ptr = UnsafePointer(to=str) var opaque\_ptr = str\_ptr.bitcast\[NoneType\]() \# ... call some foreign function that takes a void pointer === Converting data: bitcasting and byte order Bitcasting a pointer returns a new pointer that has the same memory location, but a new data type. This can be useful if you need to access different types of data from a single area of memory. This can happen when you're reading binary files, like image files, or receiving data over the network. The following sample processes a format that consists of chunks of data, where each chunk contains a variable number of 32-bit integers. Each chunk begins with an 8-bit integer that identifies the number of values in the chunk. def read\_chunks( var ptr: UnsafePointer\[mut=False, UInt8, \_\], ) -\> List\[List\[UInt32\]\]: var chunks = List\[List\[UInt32\]\]() \# A chunk size of 0 indicates the end of the data var chunk\_size = Int(ptr\[\]) while (chunk\_size \> 0): \# Skip the 1 byte chunk\_size and get a pointer to the first \# UInt32 in the chunk var ui32\_ptr = (ptr + 1).bitcast\[UInt32\]() var chunk = List\[UInt32\](capacity=chunk\_size) for i in range(chunk\_size): chunk.append(ui32\_ptr\[i\]) \# list is not implicitly copyable, so it needs the transfer sigil (^) chunks.append(chunk^) \# Move our pointer to the next byte after the current chunk ptr += (1 + 4 \* chunk\_size) \# Read the size of the next chunk chunk\_size = Int(ptr\[\]) return chunks^When dealing with data read in from a file or from the network, you may also need to deal with byte order. Most systems use little-endian byte order (also called least-significant byte, or LSB) where the least-significant byte in a multibyte value comes first. For example, the number 1001 can be represented in hexadecimal as 0x03E9, where E9 is the least-significant byte. Represented as a 16-bit little-endian integer, the two bytes are ordered E9 03. As a 32-bit integer, it would be represented as E9 03 00 00. Big-endian or most-significant byte (MSB) ordering is the opposite: in the 32-bit case, 00 00 03 E9. MSB ordering is frequently used in file formats and when transmitting data over the network. You can use the #link("https://mojolang.org/docs/std/bit/bit/byte_swap/")[byte\_swap()] function to swap the byte order of a SIMD value from big-endian to little-endian or the reverse. For example, if the method above was reading big-endian data, you'd just need to change a single line: chunk.append(byte\_swap(ui32\_ptr\[i\])) === Working with SIMD vectors The UnsafePointer type includes #link("https://mojolang.org/docs/std/memory/unsafe_pointer/UnsafePointer/#load")[load()] and #link("https://mojolang.org/docs/std/memory/unsafe_pointer/UnsafePointer/#store")[store()] methods for performing aligned loads and stores of scalar values. It also has methods supporting strided load/store and gather/scatter. Strided load loads values from memory into a SIMD vector using an offset (the "stride") between successive memory addresses. This can be useful for extracting rows or columns from tabular data, or for extracting individual values from structured data. For example, consider the data for an RGB image, where each pixel is made up of three 8-bit values, for red, green, and blue. If you want to access just the red values, you can use a strided load or store. #figure(figph[Diagram: images strided load storage], alt: "Diagram: images strided load storage", caption: none) The following function uses the #link("https://mojolang.org/docs/std/memory/unsafe_pointer/UnsafePointer/#strided_load")[strided\_load()] and #link("https://mojolang.org/docs/std/memory/unsafe_pointer/UnsafePointer/#strided_store")[strided\_store()] methods to invert the red pixel values in an image, 8 values at a time. (Note that this function only handles images where the number of pixels is evenly divisible by eight.) def invert\_red\_channel(ptr: UnsafePointer\[mut=True, UInt8, \_\], pixel\_count: Int): \# number of values loaded or stored at a time comptime simd\_width = 8 \# bytes per pixel, which is also the stride size comptime bpp = 3 for i in range(0, pixel\_count \* bpp, simd\_width \* bpp): var red\_values = (ptr + i).strided\_load\[width=simd\_width\](bpp) \# Invert values and store them in their original locations (ptr + i).strided\_store\[width=simd\_width\](~red\_values, bpp)The #link("https://mojolang.org/docs/std/memory/unsafe_pointer/UnsafePointer/#gather")[gather()] and #link("https://mojolang.org/docs/std/memory/unsafe_pointer/UnsafePointer/#scatter")[scatter()] methods let you load or store a set of values that are stored in arbitrary locations. You do this by passing in a SIMD vector of #emph[offsets] to the current pointer. For example, when using gather(), the #emph[n]th value in the vector is loaded from (pointer address) + #emph[offset\[n\]]. === Safety Unsafe pointers are unsafe for several reasons: - Memory management is up to the user. You need to manually allocate and free memory, and/or be aware of when other APIs are allocating or freeing memory for you. - UnsafePointer values are non-nullable, but they're not guaranteed to point to #emph[initialized] memory. You're responsible for ensuring memory is initialized before you dereference it. - UnsafePointer does have an origin parameter so Mojo can track the origin of the data it points to, but it also provides unsafe APIs. For example, when you do pointer arithmetic, the compiler doesn't do any bounds checking.