Serenity Operating System
1# SerenityOS Patterns
2
3## Introduction
4
5Over time numerous reoccurring patterns have emerged from or were adopted by
6the serenity code base. This document aims to track and describe them, so they
7can be propagated further and the code base can be kept consistent.
8
9## `TRY(...)` Error Handling
10
11The `TRY(..)` macro is used for error propagation in the serenity code base.
12The goal being to reduce the amount of boiler plate error code required to
13properly handle and propagate errors throughout the code base.
14
15Any code surrounded by `TRY(..)` will attempt to be executed, and any error
16will immediately be returned from the function. If no error occurs then the
17result of the contents of the TRY will be the result of the macro's execution.
18
19### Examples:
20
21Example from LibGUI:
22
23```cpp
24#include <AK/Try.h>
25
26... snip ...
27
28ErrorOr<NonnullRefPtr<Menu>> Window::try_add_menu(String name)
29{
30 auto menu = TRY(m_menubar->try_add_menu({}, move(name)));
31 if (m_window_id) {
32 menu->realize_menu_if_needed();
33 ConnectionToWindowServer::the().async_add_menu(m_window_id, menu->menu_id());
34 }
35 return menu;
36}
37```
38
39Example from the Kernel:
40
41```cpp
42#include <AK/Try.h>
43
44... snip ...
45
46ErrorOr<Region*> AddressSpace::allocate_region(VirtualRange const& range, StringView name, int prot, AllocationStrategy strategy)
47{
48 VERIFY(range.is_valid());
49 OwnPtr<KString> region_name;
50 if (!name.is_null())
51 region_name = TRY(KString::try_create(name));
52 auto vmobject = TRY(AnonymousVMObject::try_create_with_size(range.size(), strategy));
53 auto region = TRY(Region::try_create_user_accessible(range, move(vmobject), 0, move(region_name), prot_to_region_access_flags(prot), Region::Cacheable::Yes, false));
54 TRY(region->map(page_directory()));
55 return add_region(move(region));
56}
57```
58
59Note: Our `TRY(...)` macro functions similarly to the `?` [operator in rust](https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator).
60
61## `MUST(...)` Error Handling
62
63The `MUST(...)` macro is similar to `TRY(...)` except the macro enforces that
64the code run inside the macro must succeed, otherwise we assert.
65
66Note that `MUST(...)` should not be used as a replacement for `TRY(...)` in cases where error propagation is not (currently) possible.
67Instead, the `release_value_but_fixme_should_propagate_errors()` method of `ErrorOr<>` should be used to retrieve the value
68and to mark the location for future improvement. `MUST(...)` is reserved for cases where we determine through other circumstances that it
69should not be possible for the code inside the macro to fail or if a failure is serious enough that the program _needs_ to crash.
70
71### Example:
72
73```cpp
74#include <AK/Vector.h>
75
76... snip ...
77
78ErrorOr<void> insert_one_to_onehundred(Vector<int>& vector)
79{
80 TRY(vector.try_ensure_capacity(vector.size() + 100));
81
82 for (int i = 1; i <= 100; i++) {
83 // We previously made sure that we allocated enough space, so the append operation shouldn't ever fail.
84 MUST(vector.try_append(i));
85 }
86
87 return {};
88}
89```
90
91## Fallible Constructors
92
93The usual C++ constructors are incompatible with SerenityOS' method of handling errors,
94as potential errors are passed using the `ErrorOr` return type. As a replacement, classes
95that require fallible operations during their construction define a static function that
96is fallible instead.
97
98This fallible function (which should usually be named `create`) will handle any errors while
99preparing arguments for the internal constructor and run any required fallible operations after
100the object has been initialized. The resulting object is then returned as `ErrorOr<T>` or
101`ErrorOr<NonnullOwnPtr<T>>`.
102
103### Example:
104
105```cpp
106class Decompressor {
107public:
108 static ErrorOr<NonnullOwnPtr<Decompressor>> create(NonnullOwnPtr<Core::Stream::Stream> stream)
109 {
110 auto buffer = TRY(CircularBuffer::create_empty(32 * KiB));
111 auto decompressor = TRY(adopt_nonnull_own_or_enomem(new (nothrow) Decompressor(move(stream), move(buffer))));
112 TRY(decompressor->initialize_settings_from_header());
113 return decompressor;
114 }
115
116... snip ...
117
118private:
119 Decompressor(NonnullOwnPtr<Core::Stream::Stream> stream, CircularBuffer buffer)
120 : m_stream(move(stream))
121 , m_buffer(move(buffer))
122 {
123 }
124
125 CircularBuffer m_buffer;
126 NonnullOwnPtr<Core::Stream::Stream> m_stream;
127}
128```
129
130## The `serenity_main(..)` program entry point
131
132Serenity has moved to a pattern where executables do not expose a normal C
133main function. A `serenity_main(..)` is exposed instead. The main reasoning
134is that the `Main::Arguments` struct can provide arguments in a more idiomatic
135way that fits with the serenity API surface area. The ErrorOr<int> likewise
136allows the program to propagate errors seamlessly with the `TRY(...)` macro,
137avoiding a significant amount of clunky C style error handling.
138
139These executables are then linked with the `LibMain` library, which will link in
140the normal C `int main(int, char**)` function which will call into the programs
141`serenity_main(..)` on program startup.
142
143The creation of the pattern was documented in the following video:
144[OS hacking: A better main() for SerenityOS C++ programs](https://www.youtube.com/watch?v=5PciKJW1rUc)
145
146### Examples:
147
148A function `main(..)` would normally look something like:
149
150```cpp
151int main(int argc, char** argv)
152{
153 return 0;
154}
155```
156
157Instead, `serenity_main(..)` is defined like this:
158
159```cpp
160#include <LibMain/Main.h>
161
162ErrorOr<int> serenity_main(Main::Arguments arguments)
163{
164 return 0;
165}
166```
167
168## Intrusive Lists
169
170[Intrusive lists](https://www.data-structures-in-practice.com/intrusive-linked-lists/) are common in the Kernel and in some specific cases
171are used in the SerenityOS userland. A data structure is said to be
172"intrusive" when each element holds the metadata that tracks the
173element's membership in the data structure. In the case of a list, this
174means that every element in an intrusive linked list has a node embedded
175inside it. The main advantage of intrusive
176data structures is you don't need to worry about handling out of memory (OOM)
177on insertion into the data structure. This means error handling code is
178much simpler than say, using a `Vector` in environments that need to be durable
179to OOM.
180
181The common pattern for declaring an intrusive list is to add the storage
182for the intrusive list node as a private member. A public type alias is
183then used to expose the list type to anyone who might need to create it.
184Here is an example from the `Region` class in the Kernel:
185
186```cpp
187class Region final
188 : public Weakable<Region> {
189
190public:
191
192... snip ...
193
194private:
195 bool m_syscall_region : 1 { false };
196
197 IntrusiveListNode<Region> m_memory_manager_list_node;
198 IntrusiveListNode<Region> m_vmobject_list_node;
199
200public:
201 using ListInMemoryManager = IntrusiveList<&Region::m_memory_manager_list_node>;
202 using ListInVMObject = IntrusiveList<&Region::m_vmobject_list_node>;
203};
204```
205
206You can then use the list by referencing the public type alias like so:
207
208```cpp
209class MemoryManager {
210
211... snip ...
212
213 Region::ListInMemoryManager m_kernel_regions;
214 Vector<UsedMemoryRange> m_used_memory_ranges;
215 Vector<PhysicalMemoryRange> m_physical_memory_ranges;
216 Vector<ContiguousReservedMemoryRange> m_reserved_memory_ranges;
217};
218```
219
220## Static Assertions of the size of a type
221
222It's a universal pattern to use `static_assert` to validate the size of a
223type matches the author's expectations. Unfortunately when these assertions
224fail they don't give you the values that actually caused the failure. This
225forces one to go investigate by printing out the size, or checking it in a
226debugger, etc.
227
228For this reason `AK::AssertSize` was added. It exploits the fact that the
229compiler will emit template argument values for compiler errors to provide
230debugging information. Instead of getting no information you'll get the actual
231type sizes in your compiler error output.
232
233Example Usage:
234
235```cpp
236#include <AK/StdLibExtras.h>
237
238struct Empty { };
239
240static_assert(AssertSize<Empty, 1>());
241```
242
243## String View Literals
244
245`AK::StringView` support for `operator"" sv` which is a special string literal operator that was added as of
246[C++17 to enable `std::string_view` literals](https://en.cppreference.com/w/cpp/string/basic_string_view/operator%22%22sv).
247
248```cpp
249[[nodiscard]] ALWAYS_INLINE constexpr AK::StringView operator"" sv(const char* cstring, size_t length)
250{
251 return AK::StringView(cstring, length);
252}
253```
254
255This allows `AK::StringView` to be constructed from string literals with no runtime
256cost to find the string length, and the data the `AK::StringView` points to will
257reside in the data section of the binary.
258
259Example Usage:
260```cpp
261#include <AK/String.h>
262#include <AK/StringView.h>
263#include <LibTest/TestCase.h>
264
265TEST_CASE(string_view_literal_operator)
266{
267 StringView literal_view = "foo"sv;
268 String test_string = "foo";
269
270 EXPECT_EQ(literal_view.length(), test_string.length());
271 EXPECT_EQ(literal_view, test_string);
272}
273```
274
275## Source Location
276
277C++20 added std::source_location, which lets you capture the
278callers __FILE__ / __LINE__ / __FUNCTION__ etc as a default
279argument to functions.
280See: https://en.cppreference.com/w/cpp/utility/source_location
281
282`AK::SourceLocation` is the implementation of this feature in
283SerenityOS. It's become the idiomatic way to capture the location
284when adding extra debugging instrumentation, without resorting to
285littering the code with preprocessor macros.
286
287To use it, you can add the `AK::SourceLocation` as a default argument
288to any function, using `AK::SourceLocation::current()` to initialize the
289default argument.
290
291Example Usage:
292```cpp
293#include <AK/SourceLocation.h>
294#include <AK/StringView.h>
295
296static StringView example_fn(const SourceLocation& loc = SourceLocation::current())
297{
298 return loc.function_name();
299}
300
301int main(int, char**)
302{
303 return example_fn().length();
304}
305```
306
307If you only want to only capture `AK::SourceLocation` data with a certain debug macro enabled, avoid
308adding `#ifdef`'s to all functions which have the `AK::SourceLocation` argument. Since SourceLocation
309is just a simple struct, you can just declare an empty class which can be optimized away by the
310compiler, and alias both to the same name.
311
312Example Usage:
313
314```cpp
315
316#if LOCK_DEBUG
317# include <AK/SourceLocation.h>
318#endif
319
320#if LOCK_DEBUG
321using LockLocation = SourceLocation;
322#else
323struct LockLocation {
324 static constexpr LockLocation current() { return {}; }
325
326private:
327 constexpr LockLocation() = default;
328};
329#endif
330```
331
332## `type[]` vs. `Array<type>` vs. `Vector<type>` vs. `FixedArray<type>`
333
334There are four "contiguous list" / array-like types, including C-style arrays themselves. They share a lot of their API, but their use cases are all slightly different, mostly relating to how they allocate their data.
335
336Note that `Span<type>` differs from all of these types in that it provides a *view* on data owned by somebody else. The four types mentioned above all own their data, but they can provide `Span`'s which view all or part of their data. For APIs that aren't specific to the kind of list and don't need to handle resizing in any way, `Span` is a good choice.
337
338* C-style arrays are generally discouraged (and this also holds for pointer+size-style arrays when passing them around). They are only used for the implementation of other collections or in specific circumstances.
339* `Array` is a thin wrapper around C-style arrays similar to `std::array`, where the template arguments include the size of the array. It allocates its data inline, just as arrays do, and never does any dynamic allocations.
340* `Vector` is similar to `std::vector` and represents a dynamic resizable array. For most basic use cases of lists, this is the go-to collection. It has an optional inline capacity (the second template argument) which will allocate inline as the name suggests, but this is not always used. If the contents outgrow the inline capacity, Vector will automatically switch to the standard out-of-line storage. This is allocated on the heap, and the space is automatically resized and moved when more (or less) space is needed.
341* `FixedArray` is essentially a runtime-sized `Array`. It can't resize like `Vector`, but it's ideal for circumstances where the size is not known at compile time but doesn't need to change once the collection is initialized. `FixedArray` guarantees to not allocate or deallocate except for its constructor and destructor.