1 <!-- subject: {RAII}: Tragedy in three acts -->
2 <!-- date: 2023-04-01 13:13:13 -->
3 <!-- tags: c, c++, null, null pointer -->
4 <!-- categories: Articles, Techblog -->
6 <p>In
<a href=
"https://www.youtube.com/watch?v=pTMvh6VzDls">a recent
7 Computerphile video
</a>, Ian Knight talked about RAII idiom and it’s
8 application in C++ and Rust. While the video described the general
9 concepts, I felt different examples could be more clearly convey essence of
12 <p>I’ve decided to give my own explanation to hopefully better illustrate what
13 RAII is and how it relates to Rust’s ownership. Then again, for whatever
14 reason I’ve decided to write it as a play with dialogue in faux Old English so
15 it may well be even more confusing instead.
20 .play h3 { font-size:
1.25em; margin:
0 }
21 .play td { vertical-align: top }
22 /* :not(:last-child) and :not(:first-child) are used to not match cells
23 containing PRE elements. */
24 .play td:not(:last-child) {
26 font-variant: small-caps;
28 text-transform: uppercase;
31 font-variant: small-caps;
32 text-transform: uppercase;
37 .play td[
colspan=
"2"] {
40 .dir { font-style: oblique }
41 @media (max-width:
30em) {
42 .play, .play tr, .play th, .play td {
45 .play td:not(:last-child) {
48 .play td:not(:first-child) {
56 <tr><td><td><h2>Cast of characters
</h2>
57 <tr><td><td class=sm
>(In the order of appearance)
58 <tr><td>Gregory
<td>A software engineer and Putuel’s employee
60 <tr><td>Sampson
<td>A software engineer and a self-proclaimed
10× developer
61 <tr><td>Paris
<td>An apprentice returning to Putuel two summers in a row
62 <tr><td>CTO
<td>Puteal’s Chief Technical Officer spending most of his time in
64 <tr><td>Admin
<td>Administrative assistant working in Puteal Corporation’s
65 headquarters in Novear
67 <tr><td><td><h2>Act I
</h2>
69 <tr><td><h3>Scene I
</h3><td>Novear. A public place.
70 <tr><td><td class=dir
>Enter Sampson and Gregory, two senior engineers of the
71 Puteal Corporation, carrying laptops and phones
72 <tr><td>Gregory
<td>Pray tell, what doth the function’s purpose?
73 <tr><td>Sampson
<td>It doth readeth a number from a file. A task as trivial
74 as can be and yet QA reports memory leak after my change. Hence, I come
76 <tr><td><td class=dir
>Both look at a laptop showing code Sampson has written
77 [error handling omitted for brevity from all source code listings]:
78 <tr><td colspan=
2><pre>
79 double read_double(FILE *fd) {
80 char *buffer = malloc(
1024);
<i>/* allocate temporary buffer */
</i>
81 fgets(buffer,
1024, fd);
<i>/* read first line of the file */
</i>
82 return atof(buffer);
<i>/* parse and return the number */
</i>
85 <tr><td>Gregory
<td>Thine mistake is apparent. Thou didst allocate memory
86 but ne’er freed it. Verily, in C thou needs’t to explicitly free any
87 memory thou dost allocate. Submit this fix and thy code shall surely
89 <tr><td colspan=
2><pre>
90 double read_double(FILE *fd) {
91 char *buffer = malloc(
1024);
<i>/* allocate temporary buffer */
</i>
92 fgets(buffer,
1024, fd);
<i>/* read first line of the file */
</i>
93 double result = atoi(buffer);
<i>/* parse the line */
</i>
94 <ins>free(buffer);
<i>/* free the temporary buffer */
</i></ins>
95 return result;
<i>/* return parsed number */
</i>
100 <tr><td><h3>Scene II
</h3><td>A hall.
101 <tr><td><td class=dir
>Enter Puteal CTO, an apprentice called Paris and an
103 <tr><td>Paris
<td>I’ve done as Sampson beseeched of me. I’ve taken
104 the
<code>read_double
</code> function and changed it so that it doth
105 taketh file path as an argument. He hath warned me about managing
106 memory and so I’ve made sure all temporary buffers are freed.
107 Nonetheless, tests fail.
108 <tr><td colspan=
2><pre>
109 double read_double(const char *path) {
110 FILE *fd = fopen(path,
"r");
<i>/* open file */
</i>
111 <small>char *buffer = malloc(
1024);
</small>
112 <small>fgets(buffer,
1024, fd);
</small>
113 <small>double result = atof(buffer);
</small>
114 <small>free(buffer);
</small>
115 <small>return result;
</small>
118 <tr><td>CTO
<td>Thou didst well managing memory, but memory isn’t the only
119 resource that needs to be freed. Just like allocations, if thou dost
120 open a file, thou must close it anon once thou art done with it.
121 <tr><td><td class=dir
>Exit CTO and Admin towards sounds of a starting
123 <tr><td>Paris
<td>Managing resources is no easy task but I think I’m starting
124 to get the hang of it.
125 <tr><td colspan=
2><pre>
126 double read_double(const char *path) {
127 FILE *fd = fopen(path,
"r");
<i>/* open file */
</i>
128 <small>char *buffer = malloc(
1024);
</small>
129 <small>fgets(buffer,
1024, fd);
</small>
130 <ins>fclose(fd);
<i>/* close the file */
</i></ins>
131 <small>double result = atof(buffer);
</small>
132 <small>free(buffer);
</small>
133 <small>return result;
</small>
138 <tr><td><h3 na
>Scene III
</h3><td>Novear. A room in Puteal’s office.
139 <tr><td><td class=dir
>Enter Paris and Sampson they set them down on two low
141 <tr><td>Paris
<td>The end of my apprenticeship is upon me and yet my code
142 barely doth work. It canst update the sum once but as soon as I try
143 doing it for the second time, nothing happens.
144 <tr><td colspan=
2><pre>
145 double update_sum_from_file(mtx_t *lock,
148 double value = read_double(path);
<i>/* read next number from file */
</i>
149 mtx_lock(lock);
<i>/* reserve access to `sum` */
</i>
150 value += sum-
>value;
<i>/* calculate sum */
</i>
151 sum-
>value = value;
<i>/* update the sum */
</i>
152 return value;
<i>/* return new sum */
</i>
155 <tr><td>Sampson
<td>Thou hast learned well that resources need to be acquired
156 and released. But what thou art missing is that not only system memory
157 or a file descriptor are resources.
158 <tr><td>Paris
<td>So just like memory needs to be freed, files need to be
159 closed and locks needs to be unlocked!
160 <tr><td colspan=
2><pre>
161 double update_sum_from_file(mtx_t *lock,
164 double value = read_double(path);
<i>/* read next number from file */
</i>
165 mtx_lock(lock);
<i>/* reserve access to `sum` */
</i>
166 value += *sum;
<i>/* calculate sum */
</i>
167 *sum = value;
<i>/* update the sum */
</i>
168 <ins>mtx_unlock(lock);
<i>/* release `sum` */
</i></ins>
169 return value;
<i>/* return new sum */
</i>
172 <tr><td rowspan=
2>Paris
<td>I’m gladdened I partook the apprenticeship.
173 Verily, I’ve learned that resources need to be freed once they art no
174 longer used. But also that many things can be modelled like a resource.
175 <tr><td>I don’t comprehend why it all needs to be done manually?
176 <tr><td><td class=dir
>Exit Sampson while Paris monologues leaving him
180 <tr><td><td><h2>Act II
</h2>
183 <tr><td><h3>Scene I
</h3><td>Court of Puteal headquarters.
184 <tr><td><td class=dir
>Enter Sampson and Paris bearing a laptop before him
185 <tr><td>Paris
<td>Mine last year’s apprenticeship project looks naught like
187 <tr><td>Sampson
<td>Thou seest, in the year we migrated our code base to
189 <tr><td>Paris
<td>Aye, I understandeth. But I spent so much time learning
190 about managing resources and yet the new code doth not close its file.
191 <tr><td><td class=dir
>Enter Gregory and an Admin with a laptop. They all
192 look at code on Paris’ computer:
193 <tr><td colspan=
2><pre>
194 double read_double(const char *path) {
195 std::fstream file{path};
<i>/* open file */
</i>
196 double result;
<i>/* declare variable to hold result */
</i>
197 file
>> result;
<i>/* read the number */
</i>
198 return result;
<i>/* return the result */
</i>
201 <tr><td>Sampson
<td>Oh, that’s RAII.
<dfn>Resource Acquisition
202 Is Initialisation
</dfn> idiom. C++ usetht it commonly.
203 <tr><td rowspan=
2>Gregory
<td>Resource is acquired when object is initialised
204 and released when it’s destroyed. The compiler tracks lifetimes of
205 local variables and thusly handles resources for us.
206 <tr><td>By this method, all manner of resources can be managed. And
207 forsooth, for more abstract concepts without a concrete object
208 representing them, such as the concept of exclusive access to
209 a variable, a guard class can be fashioned. Gaze upon this other
211 <tr><td colspan=
2><pre>
212 double update_sum_from_file(std::mutex
&lock,
215 double value = read_double(path);
<i>/* read next number from file */
</i>
216 std::lock_guard
<std::mutex
> lock{mutex};
<i>/* reserve access to `sum` */
</i>
217 value += *sum;
<i>/* calculate sum */
</i>
218 *sum = value;
<i>/* update the sum */
</i>
219 return value;
<i>/* return new sum */
</i>
222 <tr><td>Paris
<td>I perceive it well. When the
<code>lock
</code> goes out of
223 scope, the compiler shall run its destructor, which shall release the
224 mutex. Such was my inquiry yesteryear. Thus, compilers can render
225 managing resources more automatic.
228 <tr><td><h3>Scene II
</h3><td>Novear. Sampson’s office.
229 <tr><td><td class=dir
>Enter Gregory and Sampson
230 <tr><td>Sampson
<td>Verily, this bug doth drive me mad! To make use of the
231 RAII idiom, I’ve writ an
<code>nptr
</code> template to automatically
233 <tr><td colspan=
2><pre>
236 nptr(T *ptr) : ptr(ptr) {}
<i>/* take ownership of the memory */
</i>
237 ~nptr() { delete ptr; }
<i>/* free memory when destructed */
</i>
238 T *operator-
>() { return ptr; }
239 T
&operator*() { return *ptr; }
244 <tr><td>Gregory
<td>I perceive… And what of the code that bears the bug?
245 <tr><td>Sampson
<td>'Tis naught but a simple code which calculates sum of
247 <tr><td colspan=
2><pre>
248 std::optional
<double
> try_read_double(nptr
<std::istream
> file) {
250 return *file
>> result ? std::optional{result} : std::nullopt;
253 double sum_doubles(const char *path) {
254 nptr
<std::istream
> file{new std::fstream{path}};
255 std::optional
<double
> number;
257 while ((number = try_read_double(file))) {
263 <tr><td><td class=dir
>Enter Paris with an inquiry for Sampson; seeing them
264 talk he pauses and listens in
265 <tr><td rowspan=
2>Gregory
<td>The bug lies in improper ownership tracking.
266 When ye call the
<code>try_read_double
</code> function, a copy of
267 thy
<code>nptr
</code> is made pointing to the file stream. When that
268 function doth finish, it frees that very stream, for it believes that it
269 doth own it. Alas, then you try to use it again in next loop iteration.
270 <tr><td>Why hast thou not made use of
<code>std::unique_ptr
</code>?
271 <tr><td>Sampson
<td>Ah! I prefer my own class, good sir.
272 <tr><td>Gregory
<td>Thine predicament would have been easier to discern if
273 thou hadst used standard classes. In truth, if thou wert to switch to
274 the usage of
<code>std::unique_ptr
</code>, the compiler would verily
275 find the issue and correctly refuse to compile the code.
276 <tr><td colspan=
2><pre>
277 std::optional
<double
> try_read_double(
<ins>std::unique_ptr
<std::istream
></ins> file) {
279 return *file
>> result ? std::optional{result} : std::nullopt;
282 double sum_doubles(const char *path) {
283 <ins>auto file = std::make_unique
<std::fstream
>(path);
</ins>
284 std::optional
<double
> number;
286 while ((number = try_read_double(file))) {
<i>/* compile error */
</i>
292 <tr><td><td class=dir
>Exit Gregory, exit Paris moment later
295 <tr><td><h3 na
>Scene III
</h3><td>Before Sampson’s office.
296 <tr><td><td class=dir
>Enter Gregory and Paris, meeting
297 <tr><td>Paris
<td>I’m yet again vexed. I had imagined that with RAII, the
298 compiler would handle all resource management for us?
299 <tr><td>Gregory
<td>Verily, for RAII to function, each resource must be owned
300 by a solitary object. If the ownership may be duplicated then problems
301 shall arise. Ownership may only be moved.
302 <tr><td>Paris
<td>Couldn’t compiler enforce that just like it can
303 automatically manage resources?
304 <tr><td>Gregory
<td>Mayhap the compiler can enforce it, but it’s not
305 a trivial matter. Alas, if thou art willing to spend time to model
306 ownership in a way that the compiler understands, it can prevent some of
307 the issues. However, thou wilt still require an escape hatch, for in
308 the general case, the compiler cannot prove the correctness of the code.
309 <tr><td><td class=dir
>Exit Gregory and Paris, still talking
312 <tr><td><td><h2 na
>Act III
</h2>
315 <tr><td><h3>Scene I
</h3><td>A field near Novear.
316 <tr><td><td class=dir
>Enter Gregory and Paris
317 <tr><td>Gregory
<td>Greetings, good fellow! How hast thou been since thy
319 <tr><td>Paris
<td>I’ve done as thou hast instructed and looked into Rust. It
320 is as thou hast said. I’ve recreated Sampson’s code and the compiler
321 wouldn’t let me run it:
322 <tr><td colspan=
2><pre>
323 fn try_read_double(rd: Box
<dyn std::io::Read
>) -
> Option
<f64
> {
327 fn sum_doubles(path:
&std::path::Path) -
> f64 {
328 let file = std::fs::File::open(path).unwrap();
329 let file: Box
<dyn std::io::Read
> = Box::new(file);
330 let mut result =
0.0;
331 while let Some(number) = try_read_double(file) {
337 <tr><td>Gregory
<td>Verily, the compiler hath the vision to behold the
338 migration of file’s ownership into the realm
339 of
<code>try_read_double
</code> function during the first iteration and
340 lo, it is not obtainable any longer by
<code>sum_doubles
</code>.
341 <tr><td colspan=
2><pre>
342 error[E0382]: use of moved value: `file`
344 let file: Box
<dyn std::io::Read
> = Box::new(file);
345 ---- move occurs because `file` has type `Box
<dyn std::io::Read
>`,
346 which does not implement the `Copy` trait
347 let mut result =
0.0;
348 while let Some(number) = try_read_double(file) {
349 ^^^^ value moved here, in previous
352 <tr><td>Paris
<td>Alas, I see not what thou hast forewarned me of. The
353 syntax present doth not exceed that which wouldst be used had this been
355 <tr><td colspan=
2><pre>
356 fn try_read_double(rd:
&dyn std::io::Read) -
> Option
<f64
> {
360 fn sum_doubles(path:
&std::path::Path) -
> f64 {
361 let file = std::fs::File::open(path).unwrap();
362 let file: Box
<dyn std::io::Read
> = Box::new(file);
363 let mut result =
0.0;
364 while let Some(number) = try_read_double(
&*file) {
370 <tr><td>Gregory
<td>Verily, the Rust compiler is of great wit and often
371 elides lifetimes. Nonetheless, other cases may prove more intricate.
372 <tr><td colspan=
2><pre>
373 struct Folder
<T, F
>(T, F);
375 impl
<T, F: for
<'a, 'b
> Fn(&'a mut T, &'b T)
> Folder
<T, F
> {
376 fn push(
&mut self, element:
&T) {
377 (self
.1)(
&mut self
.0, element)
381 <tr><td>Paris
<td>Surely though, albeit this code is more wordy, it is
382 advantageous if I cannot commit an error in ownership.
383 <tr><td>Gregory
<td>Verily, there be manifold factors in the selection of
384 a programming tongue. And there may be aspects which may render other
385 choices not imprudent.
390 <p>A thing to keep in mind is that the examples are somewhat contrived. For
391 example, the buffer and file object present in
<code>read_double
</code>
392 function can easily live on the stack. A real-life code wouldn’t bother
393 allocating them on the heap. Then again, I could see a beginner make
394 a mistake of trying to bypass
<code>std::unique_ptr
</code> not having a copy
395 constructor by creating objects on heap and passing pointers around.
397 <p>In the end, is this better explanation than the one in aforementioned
398 Computerphile video? I’d argue code examples represent discussed concepts
399 better though to be honest form of presentation hinders clarity of
400 explanation. Yet, I had too much fun messing around with this post so here it
403 <p>Lastly, I don’t know Old English so the dialogue is probably incorrect. I’m
404 happy to accept corrections but otherwise I don’t care that much. One
405 shouldn’t take this post too seriously.