mirror of
https://codeberg.org/ziglings/exercises.git
synced 2026-06-08 07:50:00 +00:00
Compare commits
203 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6f18ba448 | ||
|
|
cb0489904e | ||
|
|
d4a3bad19c | ||
|
|
e412619d88 | ||
|
|
3f11ad1253 | ||
|
|
2afd0f9709 | ||
|
|
a4efd69e11 | ||
|
|
f10c3d4e7a | ||
|
|
152f3a6113 | ||
|
|
ab3c498226 | ||
|
|
8c08482452 | ||
|
|
5eadcdc9f9 | ||
|
|
b8a639e798 | ||
|
|
b9cbe199ab | ||
|
|
e71175e4f8 | ||
|
|
7fcfb60cc4 | ||
|
|
34c3125411 | ||
|
|
8fc1bfac38 | ||
|
|
8e055fcd18 | ||
|
|
2472caa183 | ||
|
|
b225538aed | ||
|
|
7cb7a9948a | ||
|
|
3574fd3ae0 | ||
|
|
882c6aa0ab | ||
|
|
55a4841b07 | ||
|
|
09bae6a70e | ||
|
|
aeeb18931d | ||
|
|
63e506586f | ||
|
|
446da3ce5a | ||
|
|
5e474ea5d1 | ||
|
|
2acf192775 | ||
|
|
58f8df66d5 | ||
|
|
966c1f83af | ||
|
|
261c12d6a2 | ||
|
|
0206db8129 | ||
|
|
34bbe1347d | ||
|
|
366b597519 | ||
|
|
5307b2a338 | ||
|
|
f6a6798c8b | ||
|
|
7fae6e0607 | ||
|
|
1c6487c1e7 | ||
|
|
1166f3cfb6 | ||
|
|
52ba66c5e9 | ||
|
|
2500936153 | ||
|
|
9f314ce8e6 | ||
|
|
903c33cd0a | ||
|
|
e0259f43a7 | ||
|
|
ffde357f30 | ||
|
|
3b22bfd898 | ||
|
|
e22748d488 | ||
|
|
fcfb0e80a0 | ||
|
|
db1fef8b86 | ||
|
|
6d89dcd2de | ||
|
|
77d3b684cb | ||
|
|
3056a2b544 | ||
|
|
149a2bea50 | ||
|
|
c5ad9ff6cf | ||
|
|
8f7a629ffb | ||
|
|
d3ec872dec | ||
|
|
2cddd566bb | ||
|
|
3409760a62 | ||
|
|
9f6092aea6 | ||
|
|
95ad73013a | ||
|
|
04f4c1e32c | ||
|
|
2b5a603c43 | ||
|
|
e6d93d731a | ||
|
|
16a794fbee | ||
|
|
973ec41097 | ||
|
|
1de4e14096 | ||
|
|
0385f5d017 | ||
|
|
de3c99dddd | ||
|
|
1be6fcd7db | ||
|
|
d5fdfe708c | ||
|
|
88510735e1 | ||
|
|
07031c5daa | ||
|
|
1813c0ded8 | ||
|
|
8c119bebdc | ||
|
|
dc71c2cd06 | ||
|
|
7d03b8464d | ||
|
|
908109df2d | ||
|
|
c032550633 | ||
|
|
a83a42528e | ||
|
|
4500fba8b3 | ||
|
|
9db32388e3 | ||
|
|
53f7e015cb | ||
|
|
2e4c7a2310 | ||
|
|
0785c71532 | ||
|
|
178fd9cef3 | ||
|
|
93aa733d33 | ||
|
|
686342375e | ||
|
|
46cf5e802c | ||
|
|
2e981d408f | ||
|
|
9fb6d21ce6 | ||
|
|
9798e80deb | ||
|
|
4aeb7b83b9 | ||
|
|
8a2e40040b | ||
|
|
3ecaa34271 | ||
|
|
df3f2df50b | ||
|
|
7d4162e388 | ||
|
|
8840f201d5 | ||
|
|
bfc57ca6bb | ||
|
|
6fe9625899 | ||
|
|
0ceb0df875 | ||
|
|
3f9e390bcc | ||
|
|
07583db582 | ||
|
|
1e552a1dd6 | ||
|
|
6972af2178 | ||
|
|
91f484ed47 | ||
|
|
551008ac19 | ||
|
|
335aaafcff | ||
|
|
b98fb4d9fb | ||
|
|
9b18647fd2 | ||
|
|
639a763044 | ||
|
|
878bebb1d1 | ||
|
|
d10c31672a | ||
|
|
bf4bea929f | ||
|
|
05b324d697 | ||
|
|
8f0771d7bf | ||
|
|
d776e07af2 | ||
|
|
a04f945d36 | ||
|
|
6212297335 | ||
|
|
f87097ae54 | ||
|
|
8791b9440a | ||
|
|
af2a30e6da | ||
|
|
4927ef6a26 | ||
|
|
e8f81ddb96 | ||
|
|
b332dc879e | ||
|
|
5685c94194 | ||
|
|
3a645ac9db | ||
|
|
4340642f3c | ||
|
|
8f9daa12b2 | ||
|
|
8e30debc6a | ||
|
|
b33fd5a744 | ||
|
|
21f86f07ad | ||
|
|
a5febf58c9 | ||
|
|
209ef19596 | ||
|
|
80eb7054eb | ||
|
|
1c552813d9 | ||
|
|
acf8c483ef | ||
|
|
8cbce258a6 | ||
|
|
0010cb2a68 | ||
|
|
bb4e984a8e | ||
|
|
82d5dda273 | ||
|
|
2ffc3ee1d8 | ||
|
|
7d1184a140 | ||
|
|
e767de2337 | ||
|
|
0c9d5abccd | ||
|
|
739d5ccce8 | ||
|
|
274a49aa53 | ||
|
|
9302d10282 | ||
|
|
757826d45a | ||
|
|
ed9694e557 | ||
|
|
dc416b6c5a | ||
|
|
39e346303c | ||
|
|
11d8172136 | ||
|
|
189376944c | ||
|
|
56b220e98d | ||
|
|
850700e68e | ||
|
|
6c8f4ef507 | ||
|
|
4c291f35d5 | ||
|
|
147ff302ec | ||
|
|
c45b9cd383 | ||
|
|
26fc4fdaaa | ||
|
|
7ad02b084f | ||
|
|
a5622fd5a8 | ||
|
|
87358c610b | ||
|
|
b72bcd7e47 | ||
|
|
837e6aba3b | ||
|
|
b25f9bb66e | ||
|
|
c7e59864ca | ||
|
|
c9218fbb22 | ||
|
|
0e27b4de2f | ||
|
|
9a848f46b7 | ||
|
|
f1921e64d8 | ||
|
|
b27a6a4d1f | ||
|
|
e4a7db17b3 | ||
|
|
f281c36f79 | ||
|
|
4346d5f1d8 | ||
|
|
62ad50b71e | ||
|
|
ed93882b19 | ||
|
|
a4c1774847 | ||
|
|
e12ee5cb9e | ||
|
|
0dbe83b20e | ||
|
|
e422e24215 | ||
|
|
4134d4fa6f | ||
|
|
ad361c4b18 | ||
|
|
d4215ce5c6 | ||
|
|
fd9e9aa36e | ||
|
|
b3178e81a8 | ||
|
|
c36fe8007d | ||
|
|
7488727625 | ||
|
|
a46db7e0e8 | ||
|
|
564ea3405d | ||
|
|
16ec207471 | ||
|
|
04857aa75e | ||
|
|
6364087569 | ||
|
|
ad32e402a5 | ||
|
|
470f695968 | ||
|
|
5b3b0eb26b | ||
|
|
0661d4fd48 | ||
|
|
02db832677 | ||
|
|
06e56be9c5 | ||
|
|
f5d2c5124c |
@@ -6,4 +6,5 @@ steps:
|
|||||||
- sh ./patches/eowyn.sh
|
- sh ./patches/eowyn.sh
|
||||||
when:
|
when:
|
||||||
event: [pull_request, push, cron]
|
event: [pull_request, push, cron]
|
||||||
cron: daily*
|
branch: main
|
||||||
|
cron: daily*
|
||||||
@@ -12,10 +12,10 @@ the current Zig snapshot, setup a copy of Ziglings, and knows
|
|||||||
common language building blocks (if/then/else, loops, and
|
common language building blocks (if/then/else, loops, and
|
||||||
functions) is ready for Ziglings.
|
functions) is ready for Ziglings.
|
||||||
|
|
||||||
Ziglings is intended to be completely self-contained. If you
|
Zigling's excercises are self-contained. If you can't solve
|
||||||
can't solve an exercise from the information you've gleaned so
|
an exercise from the information you've gleaned so far from
|
||||||
far from Ziglings, then the exercise probably needs some
|
Ziglings, then the exercise probably needs some additional work.
|
||||||
additional work. Please file an issue!
|
Please file an issue!
|
||||||
|
|
||||||
If an example doesn't match a description or if something is
|
If an example doesn't match a description or if something is
|
||||||
unclear, please file an issue!
|
unclear, please file an issue!
|
||||||
@@ -38,6 +38,23 @@ Feel free to submit new exercises but please understand that they
|
|||||||
may be heavily edited or rejected entirely if we feel they don't
|
may be heavily edited or rejected entirely if we feel they don't
|
||||||
fit for one reason or another.
|
fit for one reason or another.
|
||||||
|
|
||||||
|
|
||||||
|
## AI-Generated Content
|
||||||
|
|
||||||
|
We welcome contributions from non-native English speakers and
|
||||||
|
appreciate the effort it takes to write in a foreign language.
|
||||||
|
Using AI tools to polish your English is fine.
|
||||||
|
|
||||||
|
What we don't accept is AI-generated exercises, AI-generated
|
||||||
|
comments, or pull requests produced by coding agents. Ziglings
|
||||||
|
exercises require understanding the Zig build system, the
|
||||||
|
didactic progression, and the specific conventions of this
|
||||||
|
project. That understanding doesn't come from a prompt.
|
||||||
|
|
||||||
|
If we suspect a contribution is primarily AI-generated, we will
|
||||||
|
close it.
|
||||||
|
|
||||||
|
|
||||||
## Platforms and Zig Versions
|
## Platforms and Zig Versions
|
||||||
|
|
||||||
|
|
||||||
@@ -52,6 +69,8 @@ If you run into an error in Ziglings caused by breaking changes
|
|||||||
in the latest development build of Zig, that's a new bug in
|
in the latest development build of Zig, that's a new bug in
|
||||||
Ziglings. Please file an issue...or make a pull request!
|
Ziglings. Please file an issue...or make a pull request!
|
||||||
|
|
||||||
|
For the latter, also read "The Secrets” section.
|
||||||
|
|
||||||
|
|
||||||
## Formatting
|
## Formatting
|
||||||
|
|
||||||
@@ -89,3 +108,23 @@ contribution for any other purpose.
|
|||||||
|
|
||||||
If you want to peek at the secrets, take a look at the `patches/`
|
If you want to peek at the secrets, take a look at the `patches/`
|
||||||
directory.
|
directory.
|
||||||
|
|
||||||
|
Every Ziglings exercise contains mistakes on purpose.
|
||||||
|
To keep our automated tests happy, each exercise also
|
||||||
|
has a patch in `patches/healed` that “heals” it.
|
||||||
|
|
||||||
|
When you change an exercise, you will usually need to update
|
||||||
|
its patch too. That’s where our little helper Gollum comes in:
|
||||||
|
|
||||||
|
1. In the project root, create a folder called `answers/`
|
||||||
|
2. Put your solved version of the exercise file in there
|
||||||
|
3. Back in the root, run:
|
||||||
|
`./patches/gollum <exercise-number>`<br>
|
||||||
|
For example: `./patches/gollum 106`
|
||||||
|
This will generate a shiny new patch.
|
||||||
|
|
||||||
|
Double-check everything by asking the magical Eowyn:
|
||||||
|
`./patches/eowyn`<br>
|
||||||
|
If all tests pass: You are done!
|
||||||
|
|
||||||
|
Don’t forget to commit the patch file.
|
||||||
|
|||||||
109
README.md
109
README.md
@@ -4,7 +4,7 @@ Welcome to Ziglings! This project contains a series of tiny
|
|||||||
broken programs (and one nasty surprise). By fixing them, you'll
|
broken programs (and one nasty surprise). By fixing them, you'll
|
||||||
learn how to read and write [Zig](https://ziglang.org/) code.
|
learn how to read and write [Zig](https://ziglang.org/) code.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Those broken programs need your help! (You'll also save the
|
Those broken programs need your help! (You'll also save the
|
||||||
planet from evil aliens and help some friendly elephants stick
|
planet from evil aliens and help some friendly elephants stick
|
||||||
@@ -25,27 +25,27 @@ language such as C.
|
|||||||
|
|
||||||
Each exercise is self-contained and self-explained. However,
|
Each exercise is self-contained and self-explained. However,
|
||||||
you're encouraged to also check out these Zig language resources
|
you're encouraged to also check out these Zig language resources
|
||||||
for more detail:
|
for more details:
|
||||||
|
|
||||||
* https://ziglang.org/learn/
|
* https://ziglang.org/learn/
|
||||||
* https://ziglearn.org/
|
|
||||||
* https://ziglang.org/documentation/master/
|
* https://ziglang.org/documentation/master/
|
||||||
* [Zig in Depth! (video series)](https://www.youtube.com/watch?v=MMtvGA1YhW4&list=PLtB7CL7EG7pCw7Xy1SQC53Gl8pI7aDg9t&pp=iAQB)
|
* [Zig in Depth! (video series)](https://www.youtube.com/watch?v=MMtvGA1YhW4&list=PLtB7CL7EG7pCw7Xy1SQC53Gl8pI7aDg9t&pp=iAQB)
|
||||||
|
|
||||||
Also, the [Zig community](https://github.com/ziglang/zig/wiki/Community)
|
Also, the [Zig community](https://ziglang.org/community/)
|
||||||
is incredibly friendly and helpful!
|
is incredibly friendly and helpful!
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
Install a [development build](https://ziglang.org/download/) of
|
Install a [development build](https://ziglang.org/download/) of
|
||||||
the Zig compiler. (See the "master" section of the downloads
|
the Zig compiler. (See the "master" section of the downloads
|
||||||
page.)
|
page.) Sometimes the latest build is not available there;
|
||||||
|
in that case, you can download it directly from the [build directory](https://ziglang.org/download/index.json).
|
||||||
|
|
||||||
Verify the installation and build number of `zig` like so:
|
Verify the installation and build number of `zig` like so:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ zig version
|
$ zig version
|
||||||
0.15.0-dev.xxxx+xxxxxxxxx
|
0.16.0-dev.xxxx+xxxxxxxxx
|
||||||
```
|
```
|
||||||
|
|
||||||
Clone this repository with Git:
|
Clone this repository with Git:
|
||||||
@@ -73,10 +73,12 @@ the appropriate tag.
|
|||||||
The Zig language is under very active development. In order to be
|
The Zig language is under very active development. In order to be
|
||||||
current, Ziglings tracks **development** builds of the Zig
|
current, Ziglings tracks **development** builds of the Zig
|
||||||
compiler rather than versioned **release** builds. The last
|
compiler rather than versioned **release** builds. The last
|
||||||
stable release was `0.14.1`, but Ziglings needs a dev build with
|
stable release was `0.15.2`, but Ziglings needs a dev build with
|
||||||
pre-release version "0.15.0" and a build number at least as high
|
pre-release version "0.16.0" and a build number at least as high
|
||||||
as that shown in the example version check above.
|
as that shown in the example version check above.
|
||||||
|
|
||||||
|
**Hint**: You can find a version change summary at the end of this README.
|
||||||
|
|
||||||
It is likely that you'll download a build which is _greater_ than
|
It is likely that you'll download a build which is _greater_ than
|
||||||
the minimum.
|
the minimum.
|
||||||
|
|
||||||
@@ -84,46 +86,6 @@ Once you have a build of the Zig compiler that works with
|
|||||||
Ziglings, they'll continue to work together. But keep in mind
|
Ziglings, they'll continue to work together. But keep in mind
|
||||||
that if you update one, you may need to also update the other.
|
that if you update one, you may need to also update the other.
|
||||||
|
|
||||||
|
|
||||||
### Version Changes
|
|
||||||
|
|
||||||
* *2025-08-15* zig 0.15.0-dev.1519 - changes in array list, see [#24801](https://github.com/ziglang/zig/pull/24801)
|
|
||||||
* *2025-08-08* zig 0.15.0-dev.1380 - changes in build system, see [#24588](https://github.com/ziglang/zig/pull/24588)
|
|
||||||
* *2025-07-22* zig 0.15.0-dev.1092 - various changes due to new I/O API, see [#24488](https://github.com/ziglang/zig/pull/24488)
|
|
||||||
* *2024-09-16* zig 0.14.0-dev.1573 - introduction of labeled switch, see [#21257](https://github.com/ziglang/zig/pull/21257)
|
|
||||||
* *2024-09-02* zig 0.14.0-dev.1409 - several changes in std.builtin, see [#21225](https://github.com/ziglang/zig/pull/21225)
|
|
||||||
* *2024-08-04* zig 0.14.0-dev.1224 - several changes in build system, see [#21115](https://github.com/ziglang/zig/pull/21115)
|
|
||||||
* *2024-08-04* zig 0.14.0-dev.839 - several changes in build system, see [#20580](https://github.com/ziglang/zig/pull/20580), [#20600](https://github.com/ziglang/zig/issues/20600)
|
|
||||||
* *2024-06-17* zig 0.14.0-dev.42 - changes in `std.mem.split and tokenize` - see [#15579](https://github.com/ziglang/zig/pull/15579)
|
|
||||||
* *2024-05-29* zig 0.13.0-dev.339 - rework std.Progress - see [#20059](https://github.com/ziglang/zig/pull/20059)
|
|
||||||
* *2024-03-21* zig 0.12.0-dev.3518 - change to @fieldParentPtr - see [#19470](https://github.com/ziglang/zig/pull/19470)
|
|
||||||
* *2024-03-21* zig 0.12.0-dev.3397 - rename std.os to std.posix - see [#5019](https://github.com/ziglang/zig/issues/5019)
|
|
||||||
* *2024-03-14* zig 0.12.0-dev.3302 - changes in `std.fmt` - floating-point formatting implementation - see [#19229](https://github.com/ziglang/zig/pull/19229)
|
|
||||||
* *2024-02-05* zig 0.12.0-dev.2618 - changes in `build system` - from `Step.zig_exe` to `Step.graph.zig_exe` - see [#18778](https://github.com/ziglang/zig/issues/18778)
|
|
||||||
* *2024-01-05* zig 0.12.0-dev.2043 - rename of `std.Build.FileSource` to `std.Build.LazyPath` - see [#16353](https://github.com/ziglang/zig/issues/16353)
|
|
||||||
* *2023-10-24* zig 0.12.0-dev.1243 - changes in `std.ChildProcess`: renamed exec to run - see [#5853](https://github.com/ziglang/zig/issues/5853)
|
|
||||||
* *2023-06-26* zig 0.11.0-dev.4246 - changes in compile step (now it can be null)
|
|
||||||
* *2023-06-26* zig 0.11.0-dev.3853 - removal of destination type from all cast builtins
|
|
||||||
* *2023-06-20* zig 0.11.0-dev.3747 - `@enumToInt` is now `@intFromEnum` and `@intToFloat` is now `@floatFromInt`
|
|
||||||
* *2023-05-25* zig 0.11.0-dev.3295 - `std.debug.TTY` is now `std.io.tty`
|
|
||||||
* *2023-04-30* zig 0.11.0-dev.2704 - use of the new `std.Build.ExecutableOptions.link_libc` field
|
|
||||||
* *2023-04-12* zig 0.11.0-dev.2560 - changes in `std.Build` - remove run() and install()
|
|
||||||
* *2023-04-07* zig 0.11.0-dev.2401 - fixes of the new build system - see [#212](https://github.com/ratfactor/ziglings/pull/212)
|
|
||||||
* *2023-02-21* zig 0.11.0-dev.2157 - changes in `build system` - new: parallel processing of the build steps
|
|
||||||
* *2023-02-21* zig 0.11.0-dev.1711 - changes in `for loops` - new: Multi-Object For-Loops + Struct-of-Arrays
|
|
||||||
* *2023-02-12* zig 0.11.0-dev.1638 - changes in `std.Build` cache_root now returns a directory struct
|
|
||||||
* *2023-02-04* zig 0.11.0-dev.1568 - changes in `std.Build` (combine `std.build` and `std.build.Builder` into `std.Build`)
|
|
||||||
* *2023-01-14* zig 0.11.0-dev.1302 - changes in `@addWithOverflow` (now returns a tuple) and `@typeInfo`; temporary disabled async functionality
|
|
||||||
* *2022-09-09* zig 0.10.0-dev.3978 - change in `NativeTargetInfo.detect` in build
|
|
||||||
* *2022-09-06* zig 0.10.0-dev.3880 - Ex 074 correctly fails again: comptime array len
|
|
||||||
* *2022-08-29* zig 0.10.0-dev.3685 - `@typeName()` output change, stage1 req. for async
|
|
||||||
* *2022-07-31* zig 0.10.0-dev.3385 - std lib string `fmt()` option changes
|
|
||||||
* *2022-03-19* zig 0.10.0-dev.1427 - method for getting sentinel of type changed
|
|
||||||
* *2021-12-20* zig 0.9.0-dev.2025 - `c_void` is now `anyopaque`
|
|
||||||
* *2021-06-14* zig 0.9.0-dev.137 - std.build.Id `.Custom` is now `.custom`
|
|
||||||
* *2021-04-21* zig 0.8.0-dev.1983 - std.fmt.format() `any` format string required
|
|
||||||
* *2021-02-12* zig 0.8.0-dev.1065 - std.fmt.format() `s` (string) format string required
|
|
||||||
|
|
||||||
## Advanced Usage
|
## Advanced Usage
|
||||||
|
|
||||||
It can be handy to check just a single exercise:
|
It can be handy to check just a single exercise:
|
||||||
@@ -231,7 +193,7 @@ Zig Core Language
|
|||||||
* [x] Sentinel termination
|
* [x] Sentinel termination
|
||||||
* [x] Quoted identifiers @""
|
* [x] Quoted identifiers @""
|
||||||
* [x] Anonymous structs/tuples/lists
|
* [x] Anonymous structs/tuples/lists
|
||||||
* [ ] Async <--- ironically awaiting upstream Zig updates
|
* [x] Async I/O
|
||||||
* [X] Interfaces
|
* [X] Interfaces
|
||||||
* [X] Bit manipulation
|
* [X] Bit manipulation
|
||||||
* [X] Working with C
|
* [X] Working with C
|
||||||
@@ -247,6 +209,54 @@ Zig Standard Library
|
|||||||
* [X] Tokenization
|
* [X] Tokenization
|
||||||
* [X] File handling
|
* [X] File handling
|
||||||
|
|
||||||
|
### Version Changes
|
||||||
|
|
||||||
|
* 2026-03-20 zig 0.16.0-dev.2915 - `GeneralPurposeAllocator` was changed to `DebugAllocator`
|
||||||
|
* 2026-02-04 zig 0.16.0-dev.2471 - added process.Child.Cwd, see [#31090](https://codeberg.org/ziglang/zig/pulls/31090)
|
||||||
|
* 2026-01-09 zig 0.16.0-dev.2075 - move randomness API to `std.Io`, see [#30709](https://codeberg.org/ziglang/zig/pulls/30709)
|
||||||
|
* 2026-01-07 zig 0.16.0-dev.2040 - adjust temp files, see [#30683](https://codeberg.org/ziglang/zig/pulls/30683)
|
||||||
|
* 2026-01-06 zig 0.16.0-dev.1976 - move process API to `std.Io` and changes to main/environ/argv, see [#30644](https://codeberg.org/ziglang/zig/pulls/30644)
|
||||||
|
* *2025-12-28* zig 0.16.0-dev.1859 - file system I/O integrated with the std.Io interface, see [#30232](https://codeberg.org/ziglang/zig/pulls/30232)
|
||||||
|
* *2025-11-01* zig 0.16.0-dev.1204 - more changes due to new I/O API, see [#25592](https://github.com/ziglang/zig/pull/25592)
|
||||||
|
* *2025-09-24* zig 0.16.0-dev.377 - Enable passing file content as args, see [#25228](https://github.com/ziglang/zig/pull/25228)
|
||||||
|
* *2025-09-03* zig 0.16.0-dev.164 - changes in reader, see [#25077](https://github.com/ziglang/zig/pull/25077)
|
||||||
|
* *2025-08-15* zig 0.15.0-dev.1519 - changes in array list, see [#24801](https://github.com/ziglang/zig/pull/24801)
|
||||||
|
* *2025-08-08* zig 0.15.0-dev.1380 - changes in build system, see [#24588](https://github.com/ziglang/zig/pull/24588)
|
||||||
|
* *2025-07-22* zig 0.15.0-dev.1092 - various changes due to new I/O API, see [#24488](https://github.com/ziglang/zig/pull/24488)
|
||||||
|
* *2024-09-16* zig 0.14.0-dev.1573 - introduction of labeled switch, see [#21257](https://github.com/ziglang/zig/pull/21257)
|
||||||
|
* *2024-09-02* zig 0.14.0-dev.1409 - several changes in std.builtin, see [#21225](https://github.com/ziglang/zig/pull/21225)
|
||||||
|
* *2024-08-04* zig 0.14.0-dev.1224 - several changes in build system, see [#21115](https://github.com/ziglang/zig/pull/21115)
|
||||||
|
* *2024-08-04* zig 0.14.0-dev.839 - several changes in build system, see [#20580](https://github.com/ziglang/zig/pull/20580), [#20600](https://github.com/ziglang/zig/issues/20600)
|
||||||
|
* *2024-06-17* zig 0.14.0-dev.42 - changes in `std.mem.split and tokenize` - see [#15579](https://github.com/ziglang/zig/pull/15579)
|
||||||
|
* *2024-05-29* zig 0.13.0-dev.339 - rework std.Progress - see [#20059](https://github.com/ziglang/zig/pull/20059)
|
||||||
|
* *2024-03-21* zig 0.12.0-dev.3518 - change to @fieldParentPtr - see [#19470](https://github.com/ziglang/zig/pull/19470)
|
||||||
|
* *2024-03-21* zig 0.12.0-dev.3397 - rename std.os to std.posix - see [#5019](https://github.com/ziglang/zig/issues/5019)
|
||||||
|
* *2024-03-14* zig 0.12.0-dev.3302 - changes in `std.fmt` - floating-point formatting implementation - see [#19229](https://github.com/ziglang/zig/pull/19229)
|
||||||
|
* *2024-02-05* zig 0.12.0-dev.2618 - changes in `build system` - from `Step.zig_exe` to `Step.graph.zig_exe` - see [#18778](https://github.com/ziglang/zig/issues/18778)
|
||||||
|
* *2024-01-05* zig 0.12.0-dev.2043 - rename of `std.Build.FileSource` to `std.Build.LazyPath` - see [#16353](https://github.com/ziglang/zig/issues/16353)
|
||||||
|
* *2023-10-24* zig 0.12.0-dev.1243 - changes in `std.ChildProcess`: renamed exec to run - see [#5853](https://github.com/ziglang/zig/issues/5853)
|
||||||
|
* *2023-06-26* zig 0.11.0-dev.4246 - changes in compile step (now it can be null)
|
||||||
|
* *2023-06-26* zig 0.11.0-dev.3853 - removal of destination type from all cast builtins
|
||||||
|
* *2023-06-20* zig 0.11.0-dev.3747 - `@enumToInt` is now `@intFromEnum` and `@intToFloat` is now `@floatFromInt`
|
||||||
|
* *2023-05-25* zig 0.11.0-dev.3295 - `std.debug.TTY` is now `std.io.tty`
|
||||||
|
* *2023-04-30* zig 0.11.0-dev.2704 - use of the new `std.Build.ExecutableOptions.link_libc` field
|
||||||
|
* *2023-04-12* zig 0.11.0-dev.2560 - changes in `std.Build` - remove run() and install()
|
||||||
|
* *2023-04-07* zig 0.11.0-dev.2401 - fixes of the new build system - see [#212](https://github.com/ratfactor/ziglings/pull/212)
|
||||||
|
* *2023-02-21* zig 0.11.0-dev.2157 - changes in `build system` - new: parallel processing of the build steps
|
||||||
|
* *2023-02-21* zig 0.11.0-dev.1711 - changes in `for loops` - new: Multi-Object For-Loops + Struct-of-Arrays
|
||||||
|
* *2023-02-12* zig 0.11.0-dev.1638 - changes in `std.Build` cache_root now returns a directory struct
|
||||||
|
* *2023-02-04* zig 0.11.0-dev.1568 - changes in `std.Build` (combine `std.build` and `std.build.Builder` into `std.Build`)
|
||||||
|
* *2023-01-14* zig 0.11.0-dev.1302 - changes in `@addWithOverflow` (now returns a tuple) and `@typeInfo`; temporary disabled async functionality
|
||||||
|
* *2022-09-09* zig 0.10.0-dev.3978 - change in `NativeTargetInfo.detect` in build
|
||||||
|
* *2022-09-06* zig 0.10.0-dev.3880 - Ex 074 correctly fails again: comptime array len
|
||||||
|
* *2022-08-29* zig 0.10.0-dev.3685 - `@typeName()` output change, stage1 req. for async
|
||||||
|
* *2022-07-31* zig 0.10.0-dev.3385 - std lib string `fmt()` option changes
|
||||||
|
* *2022-03-19* zig 0.10.0-dev.1427 - method for getting sentinel of type changed
|
||||||
|
* *2021-12-20* zig 0.9.0-dev.2025 - `c_void` is now `anyopaque`
|
||||||
|
* *2021-06-14* zig 0.9.0-dev.137 - std.build.Id `.Custom` is now `.custom`
|
||||||
|
* *2021-04-21* zig 0.8.0-dev.1983 - std.fmt.format() `any` format string required
|
||||||
|
* *2021-02-12* zig 0.8.0-dev.1065 - std.fmt.format() `s` (string) format string required
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Contributions are very welcome! I'm writing this to teach myself
|
Contributions are very welcome! I'm writing this to teach myself
|
||||||
@@ -259,3 +269,4 @@ tons of room for improvement:
|
|||||||
|
|
||||||
Please see [CONTRIBUTING](https://codeberg.org/ziglings/exercises/src/branch/main/CONTRIBUTING.md)
|
Please see [CONTRIBUTING](https://codeberg.org/ziglings/exercises/src/branch/main/CONTRIBUTING.md)
|
||||||
in this repo for the full details.
|
in this repo for the full details.
|
||||||
|
|
||||||
|
|||||||
357
build.zig
357
build.zig
@@ -5,7 +5,7 @@ const tests = @import("test/tests.zig");
|
|||||||
const Build = std.Build;
|
const Build = std.Build;
|
||||||
const CompileStep = Build.CompileStep;
|
const CompileStep = Build.CompileStep;
|
||||||
const Step = Build.Step;
|
const Step = Build.Step;
|
||||||
const Child = std.process.Child;
|
const Process = std.process;
|
||||||
|
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const join = std.fs.path.join;
|
const join = std.fs.path.join;
|
||||||
@@ -15,7 +15,7 @@ const print = std.debug.print;
|
|||||||
// 1) Getting Started
|
// 1) Getting Started
|
||||||
// 2) Version Changes
|
// 2) Version Changes
|
||||||
comptime {
|
comptime {
|
||||||
const required_zig = "0.15.0-dev.1519";
|
const required_zig = "0.16.0-dev.2915";
|
||||||
const current_zig = builtin.zig_version;
|
const current_zig = builtin.zig_version;
|
||||||
const min_zig = std.SemanticVersion.parse(required_zig) catch unreachable;
|
const min_zig = std.SemanticVersion.parse(required_zig) catch unreachable;
|
||||||
if (current_zig.order(min_zig) == .lt) {
|
if (current_zig.order(min_zig) == .lt) {
|
||||||
@@ -24,7 +24,7 @@ comptime {
|
|||||||
\\
|
\\
|
||||||
\\Ziglings requires development build
|
\\Ziglings requires development build
|
||||||
\\
|
\\
|
||||||
\\{}
|
\\{s}
|
||||||
\\
|
\\
|
||||||
\\or higher.
|
\\or higher.
|
||||||
\\
|
\\
|
||||||
@@ -34,7 +34,7 @@ comptime {
|
|||||||
\\
|
\\
|
||||||
\\
|
\\
|
||||||
;
|
;
|
||||||
@compileError(std.fmt.comptimePrint(error_message, .{min_zig}));
|
@compileError(std.fmt.comptimePrint(error_message, .{required_zig}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,6 +72,11 @@ pub const Exercise = struct {
|
|||||||
/// This exercise is not supported by the current Zig compiler.
|
/// This exercise is not supported by the current Zig compiler.
|
||||||
skip: bool = false,
|
skip: bool = false,
|
||||||
|
|
||||||
|
/// Hint to the user, why this has been skipped
|
||||||
|
skip_hint: ?[]const u8 = null,
|
||||||
|
|
||||||
|
timestamp: bool = false,
|
||||||
|
|
||||||
/// Returns the name of the main file with .zig stripped.
|
/// Returns the name of the main file with .zig stripped.
|
||||||
pub fn name(self: Exercise) []const u8 {
|
pub fn name(self: Exercise) []const u8 {
|
||||||
return std.fs.path.stem(self.main_file);
|
return std.fs.path.stem(self.main_file);
|
||||||
@@ -123,26 +128,18 @@ pub const logo =
|
|||||||
const progress_filename = ".progress.txt";
|
const progress_filename = ".progress.txt";
|
||||||
|
|
||||||
pub fn build(b: *Build) !void {
|
pub fn build(b: *Build) !void {
|
||||||
|
const io = b.graph.io;
|
||||||
|
|
||||||
if (!validate_exercises()) std.process.exit(2);
|
if (!validate_exercises()) std.process.exit(2);
|
||||||
|
|
||||||
use_color_escapes = false;
|
use_color_escapes = false;
|
||||||
if (std.fs.File.stderr().supportsAnsiEscapeCodes()) {
|
const stderr = std.Io.File.stderr();
|
||||||
|
if (try stderr.supportsAnsiEscapeCodes(io)) {
|
||||||
use_color_escapes = true;
|
use_color_escapes = true;
|
||||||
} else if (builtin.os.tag == .windows) {
|
} else if (builtin.os.tag == .windows) {
|
||||||
const w32 = struct {
|
if (stderr.enableAnsiEscapeCodes(io)) {
|
||||||
const DWORD = std.os.windows.DWORD;
|
use_color_escapes = true;
|
||||||
const ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
|
} else |_| {}
|
||||||
const STD_ERROR_HANDLE: DWORD = @bitCast(@as(i32, -12));
|
|
||||||
const GetStdHandle = std.os.windows.kernel32.GetStdHandle;
|
|
||||||
const GetConsoleMode = std.os.windows.kernel32.GetConsoleMode;
|
|
||||||
const SetConsoleMode = std.os.windows.kernel32.SetConsoleMode;
|
|
||||||
};
|
|
||||||
const handle = w32.GetStdHandle(w32.STD_ERROR_HANDLE).?;
|
|
||||||
var mode: w32.DWORD = 0;
|
|
||||||
if (w32.GetConsoleMode(handle, &mode) != 0) {
|
|
||||||
mode |= w32.ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
|
||||||
use_color_escapes = w32.SetConsoleMode(handle, mode) != 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (use_color_escapes) {
|
if (use_color_escapes) {
|
||||||
@@ -204,7 +201,7 @@ pub fn build(b: *Build) !void {
|
|||||||
|
|
||||||
var prng = std.Random.DefaultPrng.init(blk: {
|
var prng = std.Random.DefaultPrng.init(blk: {
|
||||||
var seed: u64 = undefined;
|
var seed: u64 = undefined;
|
||||||
try std.posix.getrandom(std.mem.asBytes(&seed));
|
io.random(std.mem.asBytes(&seed));
|
||||||
break :blk seed;
|
break :blk seed;
|
||||||
});
|
});
|
||||||
const rnd = prng.random();
|
const rnd = prng.random();
|
||||||
@@ -245,9 +242,9 @@ pub fn build(b: *Build) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (reset) |_| {
|
if (reset) |_| {
|
||||||
std.fs.cwd().deleteFile(progress_filename) catch |err| {
|
std.Io.Dir.cwd().deleteFile(io, progress_filename) catch |err| {
|
||||||
switch (err) {
|
switch (err) {
|
||||||
std.fs.Dir.DeleteFileError.FileNotFound => {},
|
std.Io.Dir.DeleteFileError.FileNotFound => {},
|
||||||
else => {
|
else => {
|
||||||
print("Unable to remove progress file, Error: {}\n", .{err});
|
print("Unable to remove progress file, Error: {}\n", .{err});
|
||||||
return err;
|
return err;
|
||||||
@@ -268,21 +265,29 @@ pub fn build(b: *Build) !void {
|
|||||||
|
|
||||||
var starting_exercise: u32 = 0;
|
var starting_exercise: u32 = 0;
|
||||||
|
|
||||||
if (std.fs.cwd().openFile(progress_filename, .{})) |progress_file| {
|
if (std.Io.Dir.cwd().openFile(io, progress_filename, .{})) |progress_file| {
|
||||||
defer progress_file.close();
|
defer progress_file.close(io);
|
||||||
|
|
||||||
const progress_file_size = try progress_file.getEndPos();
|
const progress_file_size = try progress_file.length(io);
|
||||||
|
|
||||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
var gpa = std.heap.DebugAllocator(.{}){};
|
||||||
defer _ = gpa.deinit();
|
defer _ = gpa.deinit();
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
const contents = try progress_file.readToEndAlloc(allocator, progress_file_size);
|
const contents = try allocator.alloc(u8, progress_file_size);
|
||||||
defer allocator.free(contents);
|
defer allocator.free(contents);
|
||||||
|
var file_buffer: [1024]u8 = undefined;
|
||||||
|
var file_reader = progress_file.reader(io, &file_buffer);
|
||||||
|
// try file_reader.interface.readSliceAll(contents);
|
||||||
|
const bytes_read = try file_reader.interface.readSliceShort(contents);
|
||||||
|
if (bytes_read != progress_file_size) {
|
||||||
|
return error.UnexpectedEOF;
|
||||||
|
}
|
||||||
|
|
||||||
starting_exercise = try std.fmt.parseInt(u32, contents, 10);
|
const trimmed_contents = std.mem.trim(u8, contents, "\r\n");
|
||||||
|
starting_exercise = try std.fmt.parseInt(u32, trimmed_contents, 10);
|
||||||
} else |err| {
|
} else |err| {
|
||||||
switch (err) {
|
switch (err) {
|
||||||
std.fs.File.OpenError.FileNotFound => {
|
std.Io.File.OpenError.FileNotFound => {
|
||||||
// This is fine, may be the first time tests are run or progress have been reset
|
// This is fine, may be the first time tests are run or progress have been reset
|
||||||
},
|
},
|
||||||
else => {
|
else => {
|
||||||
@@ -347,8 +352,12 @@ const ZiglingStep = struct {
|
|||||||
const self: *ZiglingStep = @alignCast(@fieldParentPtr("step", step));
|
const self: *ZiglingStep = @alignCast(@fieldParentPtr("step", step));
|
||||||
|
|
||||||
if (self.exercise.skip) {
|
if (self.exercise.skip) {
|
||||||
print("Skipping {s}\n\n", .{self.exercise.main_file});
|
print("Skipping {s}", .{self.exercise.main_file});
|
||||||
|
|
||||||
|
if (self.exercise.skip_hint) |hint|
|
||||||
|
print("\n{s}Reason: {s}{s}\n", .{ bold_text, hint, reset_text });
|
||||||
|
|
||||||
|
print("\n\n", .{});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,19 +387,18 @@ const ZiglingStep = struct {
|
|||||||
|
|
||||||
fn run(self: *ZiglingStep, exe_path: []const u8, _: std.Progress.Node) !void {
|
fn run(self: *ZiglingStep, exe_path: []const u8, _: std.Progress.Node) !void {
|
||||||
resetLine();
|
resetLine();
|
||||||
print("Checking: {s}\n", .{self.exercise.main_file});
|
|
||||||
|
|
||||||
const b = self.step.owner;
|
const b = self.step.owner;
|
||||||
|
const io = b.graph.io;
|
||||||
|
|
||||||
|
print("Checking: {s}\n", .{self.exercise.main_file});
|
||||||
|
|
||||||
// Allow up to 1 MB of stdout capture.
|
// Allow up to 1 MB of stdout capture.
|
||||||
const max_output_bytes = 1 * 1024 * 1024;
|
const max_output_bytes = 1 * 1024 * 1024;
|
||||||
|
|
||||||
const result = Child.run(.{
|
const result = Process.run(b.allocator, io, .{
|
||||||
.allocator = b.allocator,
|
|
||||||
.argv = &.{exe_path},
|
.argv = &.{exe_path},
|
||||||
.cwd = b.build_root.path.?,
|
.cwd = .{ .path = b.build_root.path.? },
|
||||||
.cwd_dir = b.build_root.handle,
|
.stdout_limit = .limited(max_output_bytes),
|
||||||
.max_output_bytes = max_output_bytes,
|
|
||||||
}) catch |err| {
|
}) catch |err| {
|
||||||
return self.step.fail("unable to spawn {s}: {s}", .{
|
return self.step.fail("unable to spawn {s}: {s}", .{
|
||||||
exe_path, @errorName(err),
|
exe_path, @errorName(err),
|
||||||
@@ -403,12 +411,13 @@ const ZiglingStep = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_output(self: *ZiglingStep, result: Child.RunResult) !void {
|
fn check_output(self: *ZiglingStep, result: Process.RunResult) !void {
|
||||||
const b = self.step.owner;
|
const b = self.step.owner;
|
||||||
|
const io = b.graph.io;
|
||||||
|
|
||||||
// Make sure it exited cleanly.
|
// Make sure it exited cleanly.
|
||||||
switch (result.term) {
|
switch (result.term) {
|
||||||
.Exited => |code| {
|
.exited => |code| {
|
||||||
if (code != 0) {
|
if (code != 0) {
|
||||||
return self.step.fail("{s} exited with error code {d} (expected {})", .{
|
return self.step.fail("{s} exited with error code {d} (expected {})", .{
|
||||||
self.exercise.main_file, code, 0,
|
self.exercise.main_file, code, 0,
|
||||||
@@ -427,12 +436,40 @@ const ZiglingStep = struct {
|
|||||||
else
|
else
|
||||||
result.stderr;
|
result.stderr;
|
||||||
|
|
||||||
// Validate the output.
|
|
||||||
// NOTE: exercise.output can never contain a CR character.
|
// NOTE: exercise.output can never contain a CR character.
|
||||||
// See https://ziglang.org/documentation/master/#Source-Encoding.
|
// See https://ziglang.org/documentation/master/#Source-Encoding.
|
||||||
const output = trimLines(b.allocator, raw_output) catch @panic("OOM");
|
const output = trimLines(b.allocator, raw_output) catch @panic("OOM");
|
||||||
const exercise_output = self.exercise.output;
|
|
||||||
if (!std.mem.eql(u8, output, self.exercise.output)) {
|
// Validate the output.
|
||||||
|
var exercise_output = self.exercise.output;
|
||||||
|
|
||||||
|
// Insert timestamp for exercise 85
|
||||||
|
if (self.exercise.timestamp) {
|
||||||
|
|
||||||
|
// Compare timestamp from exercise with now, diff < 5 seconds is valid
|
||||||
|
var ts_buf: [20]u8 = undefined;
|
||||||
|
const ts_slice = output[14..24];
|
||||||
|
const ts_value = try std.fmt.parseInt(i64, ts_slice, 10);
|
||||||
|
const ts_build = std.Io.Timestamp.now(io, .real).toSeconds();
|
||||||
|
const ts_diff = @abs(ts_build - ts_value);
|
||||||
|
const timestamp = std.fmt.bufPrint(&ts_buf, "{}", .{if (ts_diff < 5) ts_value else ts_build}) catch unreachable;
|
||||||
|
|
||||||
|
// Insert timestamp into check string
|
||||||
|
var buf: [100]u8 = undefined;
|
||||||
|
const prefix_len = 14;
|
||||||
|
const placeholder_len = 11;
|
||||||
|
|
||||||
|
@memcpy(buf[0..prefix_len], exercise_output[0..prefix_len]);
|
||||||
|
@memcpy(buf[prefix_len..][0..timestamp.len], timestamp);
|
||||||
|
const suffix = exercise_output[prefix_len + placeholder_len ..];
|
||||||
|
const suffix_dest_start = prefix_len + timestamp.len;
|
||||||
|
@memcpy(buf[suffix_dest_start..][0..suffix.len], suffix);
|
||||||
|
|
||||||
|
const total_len = prefix_len + timestamp.len + suffix.len;
|
||||||
|
exercise_output = buf[0..total_len];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!std.mem.eql(u8, output, exercise_output)) {
|
||||||
const red = red_bold_text;
|
const red = red_bold_text;
|
||||||
const reset = reset_text;
|
const reset = reset_text;
|
||||||
|
|
||||||
@@ -452,24 +489,25 @@ const ZiglingStep = struct {
|
|||||||
const progress = try std.fmt.allocPrint(b.allocator, "{d}", .{self.exercise.number()});
|
const progress = try std.fmt.allocPrint(b.allocator, "{d}", .{self.exercise.number()});
|
||||||
defer b.allocator.free(progress);
|
defer b.allocator.free(progress);
|
||||||
|
|
||||||
const file = try std.fs.cwd().createFile(
|
const file = try std.Io.Dir.cwd().createFile(
|
||||||
|
io,
|
||||||
progress_filename,
|
progress_filename,
|
||||||
.{ .read = true, .truncate = true },
|
.{ .read = true, .truncate = true },
|
||||||
);
|
);
|
||||||
defer file.close();
|
defer file.close(io);
|
||||||
|
|
||||||
try file.writeAll(progress);
|
try file.writeStreamingAll(io, progress);
|
||||||
try file.sync();
|
try file.sync(io);
|
||||||
|
|
||||||
print("{s}PASSED:\n{s}{s}\n\n", .{ green_text, output, reset_text });
|
print("{s}PASSED:\n{s}{s}\n\n", .{ green_text, output, reset_text });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_test(self: *ZiglingStep, result: Child.RunResult) !void {
|
fn check_test(self: *ZiglingStep, result: Process.RunResult) !void {
|
||||||
switch (result.term) {
|
switch (result.term) {
|
||||||
.Exited => |code| {
|
.exited => |code| {
|
||||||
if (code != 0) {
|
if (code != 0) {
|
||||||
// The test failed.
|
// The test failed.
|
||||||
const stderr = std.mem.trimRight(u8, result.stderr, " \r\n");
|
const stderr = std.mem.trimEnd(u8, result.stderr, " \r\n");
|
||||||
|
|
||||||
return self.step.fail("\n{s}", .{stderr});
|
return self.step.fail("\n{s}", .{stderr});
|
||||||
}
|
}
|
||||||
@@ -506,6 +544,11 @@ const ZiglingStep = struct {
|
|||||||
// Enable C support for exercises that use C functions.
|
// Enable C support for exercises that use C functions.
|
||||||
if (self.exercise.link_libc) {
|
if (self.exercise.link_libc) {
|
||||||
zig_args.append("-lc") catch @panic("OOM");
|
zig_args.append("-lc") catch @panic("OOM");
|
||||||
|
zig_args.append("-fllvm") catch @panic("OOM");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b.reference_trace) |rt| {
|
||||||
|
zig_args.append(b.fmt("-freference-trace={}", .{rt})) catch @panic("OOM");
|
||||||
}
|
}
|
||||||
|
|
||||||
zig_args.append(b.pathFromRoot(path)) catch @panic("OOM");
|
zig_args.append(b.pathFromRoot(path)) catch @panic("OOM");
|
||||||
@@ -524,7 +567,7 @@ const ZiglingStep = struct {
|
|||||||
.exe => self.exercise.name(),
|
.exe => self.exercise.name(),
|
||||||
.@"test" => "test",
|
.@"test" => "test",
|
||||||
};
|
};
|
||||||
const sep = std.fs.path.sep_str;
|
const sep = std.Io.Dir.path.sep_str;
|
||||||
const root_path = exe_dir.?.root_dir.path.?;
|
const root_path = exe_dir.?.root_dir.path.?;
|
||||||
const sub_path = exe_dir.?.subPathOrDot();
|
const sub_path = exe_dir.?.subPathOrDot();
|
||||||
const exe_path = b.fmt("{s}{s}{s}{s}{s}", .{ root_path, sep, sub_path, sep, exe_name });
|
const exe_path = b.fmt("{s}{s}{s}{s}{s}", .{ root_path, sep, sub_path, sep, exe_name });
|
||||||
@@ -550,6 +593,8 @@ const ZiglingStep = struct {
|
|||||||
|
|
||||||
fn printErrors(self: *ZiglingStep) void {
|
fn printErrors(self: *ZiglingStep) void {
|
||||||
resetLine();
|
resetLine();
|
||||||
|
const b = self.step.owner;
|
||||||
|
const io = b.graph.io;
|
||||||
|
|
||||||
// Display error/warning messages.
|
// Display error/warning messages.
|
||||||
if (self.step.result_error_msgs.items.len > 0) {
|
if (self.step.result_error_msgs.items.len > 0) {
|
||||||
@@ -562,12 +607,15 @@ const ZiglingStep = struct {
|
|||||||
|
|
||||||
// Render compile errors at the bottom of the terminal.
|
// Render compile errors at the bottom of the terminal.
|
||||||
// TODO: use the same ttyconf from the builder.
|
// TODO: use the same ttyconf from the builder.
|
||||||
const ttyconf: std.io.tty.Config = if (use_color_escapes)
|
const color: std.zig.Color = if (use_color_escapes)
|
||||||
.escape_codes
|
.on
|
||||||
else
|
else
|
||||||
.no_color;
|
.off;
|
||||||
if (self.step.result_error_bundle.errorMessageCount() > 0) {
|
if (self.step.result_error_bundle.errorMessageCount() > 0) {
|
||||||
self.step.result_error_bundle.renderToStdErr(.{ .ttyconf = ttyconf });
|
self.step.result_error_bundle.renderToStderr(io, .{}, color) catch |err| {
|
||||||
|
print("{}\n", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -586,7 +634,7 @@ pub fn trimLines(allocator: std.mem.Allocator, buf: []const u8) ![]const u8 {
|
|||||||
var iter = std.mem.splitSequence(u8, buf, " \n");
|
var iter = std.mem.splitSequence(u8, buf, " \n");
|
||||||
while (iter.next()) |line| {
|
while (iter.next()) |line| {
|
||||||
// TODO: trimming CR characters is probably not necessary.
|
// TODO: trimming CR characters is probably not necessary.
|
||||||
const data = std.mem.trimRight(u8, line, " \r");
|
const data = std.mem.trimEnd(u8, line, " \r");
|
||||||
try list.appendSlice(allocator, data);
|
try list.appendSlice(allocator, data);
|
||||||
try list.append(allocator, '\n');
|
try list.append(allocator, '\n');
|
||||||
}
|
}
|
||||||
@@ -595,7 +643,7 @@ pub fn trimLines(allocator: std.mem.Allocator, buf: []const u8) ![]const u8 {
|
|||||||
|
|
||||||
// Remove the trailing LF character, that is always present in the exercise
|
// Remove the trailing LF character, that is always present in the exercise
|
||||||
// output.
|
// output.
|
||||||
return std.mem.trimRight(u8, result, "\n");
|
return std.mem.trimEnd(u8, result, "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prints a message to stderr.
|
/// Prints a message to stderr.
|
||||||
@@ -647,7 +695,7 @@ fn validate_exercises() bool {
|
|||||||
|
|
||||||
var iter = std.mem.splitScalar(u8, ex.output, '\n');
|
var iter = std.mem.splitScalar(u8, ex.output, '\n');
|
||||||
while (iter.next()) |line| {
|
while (iter.next()) |line| {
|
||||||
const output = std.mem.trimRight(u8, line, " \r");
|
const output = std.mem.trimEnd(u8, line, " \r");
|
||||||
if (output.len != line.len) {
|
if (output.len != line.len) {
|
||||||
print("exercise {s} output field lines have trailing whitespace\n", .{
|
print("exercise {s} output field lines have trailing whitespace\n", .{
|
||||||
ex.main_file,
|
ex.main_file,
|
||||||
@@ -680,7 +728,7 @@ const exercises = [_]Exercise{
|
|||||||
\\most part, you'll be taking directions from the Zig
|
\\most part, you'll be taking directions from the Zig
|
||||||
\\compiler itself.)
|
\\compiler itself.)
|
||||||
\\
|
\\
|
||||||
,
|
, // pay attention to the comma
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "002_std.zig",
|
.main_file = "002_std.zig",
|
||||||
@@ -712,7 +760,7 @@ const exercises = [_]Exercise{
|
|||||||
\\Ziggy played guitar
|
\\Ziggy played guitar
|
||||||
\\Jamming good with Andrew Kelley
|
\\Jamming good with Andrew Kelley
|
||||||
\\And the Spiders from Mars
|
\\And the Spiders from Mars
|
||||||
,
|
, // pay attention to the comma
|
||||||
.hint = "Please fix the lyrics!",
|
.hint = "Please fix the lyrics!",
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
@@ -722,7 +770,7 @@ const exercises = [_]Exercise{
|
|||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "009_if.zig",
|
.main_file = "009_if.zig",
|
||||||
.output = "Foo is 1!",
|
.output = "Foo is 42!",
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "010_if2.zig",
|
.main_file = "010_if2.zig",
|
||||||
@@ -849,7 +897,7 @@ const exercises = [_]Exercise{
|
|||||||
\\ <span style="color: #00ff00">Green</span>
|
\\ <span style="color: #00ff00">Green</span>
|
||||||
\\ <span style="color: #0000ff">Blue</span>
|
\\ <span style="color: #0000ff">Blue</span>
|
||||||
\\</p>
|
\\</p>
|
||||||
,
|
, // pay attention to the comma
|
||||||
.hint = "I'm feeling blue about this.",
|
.hint = "I'm feeling blue about this.",
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
@@ -861,7 +909,7 @@ const exercises = [_]Exercise{
|
|||||||
.output =
|
.output =
|
||||||
\\Character 1 - G:20 H:100 XP:10
|
\\Character 1 - G:20 H:100 XP:10
|
||||||
\\Character 2 - G:10 H:100 XP:20
|
\\Character 2 - G:10 H:100 XP:20
|
||||||
,
|
, // pay attention to the comma
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "039_pointers.zig",
|
.main_file = "039_pointers.zig",
|
||||||
@@ -885,7 +933,7 @@ const exercises = [_]Exercise{
|
|||||||
.output =
|
.output =
|
||||||
\\Wizard (G:10 H:100 XP:20)
|
\\Wizard (G:10 H:100 XP:20)
|
||||||
\\ Mentor: Wizard (G:10000 H:100 XP:2340)
|
\\ Mentor: Wizard (G:10000 H:100 XP:2340)
|
||||||
,
|
, // pay attention to the comma
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "044_quiz5.zig",
|
.main_file = "044_quiz5.zig",
|
||||||
@@ -929,7 +977,7 @@ const exercises = [_]Exercise{
|
|||||||
.output =
|
.output =
|
||||||
\\Hand1: A 4 K 8
|
\\Hand1: A 4 K 8
|
||||||
\\Hand2: 5 2 Q J
|
\\Hand2: 5 2 Q J
|
||||||
,
|
, // pay attention to the comma
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "053_slices2.zig",
|
.main_file = "053_slices2.zig",
|
||||||
@@ -962,7 +1010,7 @@ const exercises = [_]Exercise{
|
|||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "060_floats.zig",
|
.main_file = "060_floats.zig",
|
||||||
.output = "Shuttle liftoff weight: 2032 metric tons",
|
.output = "Shuttle liftoff weight: 2.032e3 metric tons",
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "061_coercions.zig",
|
.main_file = "061_coercions.zig",
|
||||||
@@ -1027,8 +1075,7 @@ const exercises = [_]Exercise{
|
|||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "074_comptime9.zig",
|
.main_file = "074_comptime9.zig",
|
||||||
.output = "My llama value is 2.",
|
.output = "MouseLlama joins the crew!",
|
||||||
.skip = true,
|
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "075_quiz8.zig",
|
.main_file = "075_quiz8.zig",
|
||||||
@@ -1064,95 +1111,123 @@ const exercises = [_]Exercise{
|
|||||||
.main_file = "082_anonymous_structs3.zig",
|
.main_file = "082_anonymous_structs3.zig",
|
||||||
.output =
|
.output =
|
||||||
\\"0"(bool):true "1"(bool):false "2"(i32):42 "3"(f32):3.141592
|
\\"0"(bool):true "1"(bool):false "2"(i32):42 "3"(f32):3.141592
|
||||||
,
|
, // pay attention to the comma
|
||||||
.hint = "This one is a challenge! But you have everything you need.",
|
.hint = "This one is a challenge! But you have everything you need.",
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "083_anonymous_lists.zig",
|
.main_file = "083_anonymous_lists.zig",
|
||||||
.output = "I say hello!",
|
.output = "I say hello!",
|
||||||
},
|
},
|
||||||
|
.{
|
||||||
|
.main_file = "084_interfaces.zig",
|
||||||
|
.output =
|
||||||
|
\\=== Doctor Zoraptera's Insect Report ===
|
||||||
|
\\Ant is alive.
|
||||||
|
\\Bee visited 17 flowers.
|
||||||
|
\\Grasshopper hopped 32 meters.
|
||||||
|
, // pay attention to the comma
|
||||||
|
},
|
||||||
|
|
||||||
// Skipped because of https://github.com/ratfactor/ziglings/issues/163
|
// Skipped because of https://github.com/ratfactor/ziglings/issues/163
|
||||||
// direct link: https://github.com/ziglang/zig/issues/6025
|
// direct link: https://github.com/ziglang/zig/issues/6025
|
||||||
.{
|
.{
|
||||||
.main_file = "084_async.zig",
|
.main_file = "085_async.zig",
|
||||||
.output = "foo() A",
|
.output = "Current time: <timestamp>s since epoch",
|
||||||
.hint = "Read the facts. Use the facts.",
|
.timestamp = true,
|
||||||
.skip = true,
|
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "085_async2.zig",
|
.main_file = "086_async2.zig",
|
||||||
.output = "Hello async!",
|
.output = "Computing... The answer is: 42",
|
||||||
.skip = true,
|
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "086_async3.zig",
|
.main_file = "087_async3.zig",
|
||||||
.output = "5 4 3 2 1",
|
|
||||||
.skip = true,
|
|
||||||
},
|
|
||||||
.{
|
|
||||||
.main_file = "087_async4.zig",
|
|
||||||
.output = "1 2 3 4 5",
|
|
||||||
.skip = true,
|
|
||||||
},
|
|
||||||
.{
|
|
||||||
.main_file = "088_async5.zig",
|
|
||||||
.output = "Example Title.",
|
|
||||||
.skip = true,
|
|
||||||
},
|
|
||||||
.{
|
|
||||||
.main_file = "089_async6.zig",
|
|
||||||
.output = ".com: Example Title, .org: Example Title.",
|
|
||||||
.skip = true,
|
|
||||||
},
|
|
||||||
.{
|
|
||||||
.main_file = "090_async7.zig",
|
|
||||||
.output = "beef? BEEF!",
|
|
||||||
.skip = true,
|
|
||||||
},
|
|
||||||
.{
|
|
||||||
.main_file = "091_async8.zig",
|
|
||||||
.output = "ABCDEF",
|
|
||||||
.skip = true,
|
|
||||||
},
|
|
||||||
|
|
||||||
.{
|
|
||||||
.main_file = "092_interfaces.zig",
|
|
||||||
.output =
|
.output =
|
||||||
\\Daily Insect Report:
|
\\1 + 2 = 3
|
||||||
\\Ant is alive.
|
\\6 * 7 = 42
|
||||||
\\Bee visited 17 flowers.
|
\\Total: 45
|
||||||
\\Grasshopper hopped 32 meters.
|
, // pay attention to the comma
|
||||||
,
|
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "093_hello_c.zig",
|
.main_file = "088_async4.zig",
|
||||||
|
.output =
|
||||||
|
\\Task 1 done.
|
||||||
|
\\Task 2 done.
|
||||||
|
\\Task 3 done.
|
||||||
|
\\All tasks finished!
|
||||||
|
, // pay attention to the comma
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.main_file = "089_async5.zig",
|
||||||
|
.output =
|
||||||
|
\\Starting long computation...
|
||||||
|
\\Canceling slow task...
|
||||||
|
\\Task was canceled, cleaning up.
|
||||||
|
\\Task returned: 0
|
||||||
|
, // pay attention to the comma
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.main_file = "090_async6.zig",
|
||||||
|
.output = "Hare: I'm fast!",
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.main_file = "091_async7.zig",
|
||||||
|
.output = "Counter: 400",
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.main_file = "092_async8.zig",
|
||||||
|
.output = "Sum of 1..10 = 55",
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.main_file = "093_async9.zig",
|
||||||
|
.output = "Worker 1 found signal start over threshold at index 12!",
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.main_file = "094_async10.zig",
|
||||||
|
.output =
|
||||||
|
\\Starting critical section...
|
||||||
|
\\Critical section completed safely.
|
||||||
|
\\Task result: All data saved.
|
||||||
|
, // pay attention to the comma
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.main_file = "095_quiz_async.zig",
|
||||||
|
.output =
|
||||||
|
\\=== Doctor Zoraptera's Garden Report ===
|
||||||
|
\\Temperature : 23C
|
||||||
|
\\Humidity : 63%
|
||||||
|
\\Wind : 13 km/h
|
||||||
|
\\Readings : 9
|
||||||
|
\\Bee-friendly conditions! Expect high pollination.
|
||||||
|
, // pay attention to the comma
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.main_file = "096_hello_c.zig",
|
||||||
.output = "Hello C from Zig! - C result is 17 chars written.",
|
.output = "Hello C from Zig! - C result is 17 chars written.",
|
||||||
.link_libc = true,
|
.link_libc = true,
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "094_c_math.zig",
|
.main_file = "097_c_math.zig",
|
||||||
.output = "The normalized angle of 765.2 degrees is 45.2 degrees.",
|
.output = "The normalized angle of 765.2 degrees is 45.2 degrees.",
|
||||||
.link_libc = true,
|
.link_libc = true,
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "095_for3.zig",
|
.main_file = "098_for3.zig",
|
||||||
.output = "1 2 4 7 8 11 13 14 16 17 19",
|
.output = "1 2 4 7 8 11 13 14 16 17 19\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15",
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "096_memory_allocation.zig",
|
.main_file = "099_memory_allocation.zig",
|
||||||
.output = "Running Average: 0.30 0.25 0.20 0.18 0.22",
|
.output = "Running Average: 0.30 0.25 0.20 0.18 0.22",
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "097_bit_manipulation.zig",
|
.main_file = "100_bit_manipulation.zig",
|
||||||
.output = "x = 1011; y = 1101",
|
.output = "x = 1011; y = 1101",
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "098_bit_manipulation2.zig",
|
.main_file = "101_bit_manipulation2.zig",
|
||||||
.output = "Is this a pangram? true!",
|
.output = "Is this a pangram? true!",
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "099_formatting.zig",
|
.main_file = "102_formatting.zig",
|
||||||
.output =
|
.output =
|
||||||
\\
|
\\
|
||||||
\\ X | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
\\ X | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
||||||
@@ -1189,25 +1264,25 @@ const exercises = [_]Exercise{
|
|||||||
,
|
,
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "100_for4.zig",
|
.main_file = "103_for4.zig",
|
||||||
.output = "Arrays match!",
|
.output = "Arrays match!",
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "101_for5.zig",
|
.main_file = "104_for5.zig",
|
||||||
.output =
|
.output =
|
||||||
\\1. Wizard (Gold: 25, XP: 40)
|
\\1. Wizard (Gold: 25, XP: 40)
|
||||||
\\2. Bard (Gold: 11, XP: 17)
|
\\2. Bard (Gold: 11, XP: 17)
|
||||||
\\3. Bard (Gold: 5, XP: 55)
|
\\3. Bard (Gold: 5, XP: 55)
|
||||||
\\4. Warrior (Gold: 7392, XP: 21)
|
\\4. Warrior (Gold: 7392, XP: 21)
|
||||||
,
|
, // pay attention to the comma
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "102_testing.zig",
|
.main_file = "105_testing.zig",
|
||||||
.output = "",
|
.output = "",
|
||||||
.kind = .@"test",
|
.kind = .@"test",
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "103_tokenization.zig",
|
.main_file = "106_tokenization.zig",
|
||||||
.output =
|
.output =
|
||||||
\\My
|
\\My
|
||||||
\\name
|
\\name
|
||||||
@@ -1225,10 +1300,10 @@ const exercises = [_]Exercise{
|
|||||||
\\and
|
\\and
|
||||||
\\despair
|
\\despair
|
||||||
\\This little poem has 15 words!
|
\\This little poem has 15 words!
|
||||||
,
|
, // pay attention to the comma
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "104_threading.zig",
|
.main_file = "107_threading.zig",
|
||||||
.output =
|
.output =
|
||||||
\\Starting work...
|
\\Starting work...
|
||||||
\\thread 1: started.
|
\\thread 1: started.
|
||||||
@@ -1239,35 +1314,35 @@ const exercises = [_]Exercise{
|
|||||||
\\thread 1: finished.
|
\\thread 1: finished.
|
||||||
\\thread 3: finished.
|
\\thread 3: finished.
|
||||||
\\Zig is cool!
|
\\Zig is cool!
|
||||||
,
|
, // pay attention to the comma
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "105_threading2.zig",
|
.main_file = "108_threading2.zig",
|
||||||
.output = "PI ≈ 3.14159265",
|
.output = "PI ≈ 3.14159265",
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "106_files.zig",
|
.main_file = "109_files.zig",
|
||||||
.output = "Successfully wrote 18 bytes.",
|
.output = "Successfully wrote 18 bytes.",
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "107_files2.zig",
|
.main_file = "110_files2.zig",
|
||||||
.output =
|
.output =
|
||||||
\\AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
\\AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
\\Successfully Read 18 bytes: It's zigling time!
|
\\Successfully Read 18 bytes: It's zigling time!
|
||||||
,
|
, // pay attention to the comma
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "108_labeled_switch.zig",
|
.main_file = "111_labeled_switch.zig",
|
||||||
.output = "The pull request has been merged.",
|
.output = "The pull request has been merged.",
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "109_vectors.zig",
|
.main_file = "112_vectors.zig",
|
||||||
.output =
|
.output =
|
||||||
\\Max difference (old fn): 0.014
|
\\Max difference (old fn): 0.014
|
||||||
\\Max difference (new fn): 0.014
|
\\Max difference (new fn): 0.014
|
||||||
,
|
, // pay attention to the comma
|
||||||
},
|
},
|
||||||
.{ .main_file = "110_quiz9.zig", .output =
|
.{ .main_file = "113_quiz9.zig", .output =
|
||||||
\\Toggle pins with XOR on PORTB
|
\\Toggle pins with XOR on PORTB
|
||||||
\\-----------------------------
|
\\-----------------------------
|
||||||
\\ 1100 // (initial state of PORTB)
|
\\ 1100 // (initial state of PORTB)
|
||||||
@@ -1298,12 +1373,20 @@ const exercises = [_]Exercise{
|
|||||||
\\& 1110 // (bitmask)
|
\\& 1110 // (bitmask)
|
||||||
\\= 0110
|
\\= 0110
|
||||||
},
|
},
|
||||||
|
.{
|
||||||
|
.main_file = "114_packed.zig",
|
||||||
|
.output = "",
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.main_file = "115_packed2.zig",
|
||||||
|
.output = "",
|
||||||
|
},
|
||||||
.{
|
.{
|
||||||
.main_file = "999_the_end.zig",
|
.main_file = "999_the_end.zig",
|
||||||
.output =
|
.output =
|
||||||
\\
|
\\
|
||||||
\\This is the end for now!
|
\\This is the end for now!
|
||||||
\\We hope you had fun and were able to learn a lot, so visit us again when the next exercises are available.
|
\\We hope you had fun and were able to learn a lot, so visit us again when the next exercises are available.
|
||||||
,
|
, // pay attention to the comma
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
// Oh no, this is supposed to print "Hello world!" but it needs
|
// Oh no, this is supposed to print "Hello world!" but it needs
|
||||||
// your help.
|
// your help.
|
||||||
//
|
//
|
||||||
// Zig functions are private by default but the main() function
|
// Zig functions are private by default, but the main() function
|
||||||
// should be public.
|
// must be public.
|
||||||
//
|
//
|
||||||
// A function is made public with the "pub" statement like so:
|
// A function is made public with the "pub" statement like so:
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -21,13 +21,13 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub fn main() void {
|
pub fn main() void {
|
||||||
const foo = 1;
|
const foo = 42;
|
||||||
|
|
||||||
// Please fix this condition:
|
// Please fix this condition:
|
||||||
if (foo) {
|
if (foo) {
|
||||||
// We want our program to print this message!
|
// We want our program to print this message!
|
||||||
std.debug.print("Foo is 1!\n", .{});
|
std.debug.print("Foo is 42!\n", .{});
|
||||||
} else {
|
} else {
|
||||||
std.debug.print("Foo is not 1!\n", .{});
|
std.debug.print("Foo is not 42!\n", .{});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
//
|
//
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// The "continue expression" executes every time the loop restarts
|
// The "continue expression" executes every single time the loop restarts,
|
||||||
// whether the "continue" statement happens or not.
|
// even when a `continue` statement skips the rest of the loop body.
|
||||||
//
|
//
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// example that takes two parameters. As you can see, parameters
|
// example that takes two parameters. As you can see, parameters
|
||||||
// are declared just like any other types ("name": "type"):
|
// are declared just like any other types ("name": "type"):
|
||||||
//
|
//
|
||||||
// fn myFunction(number: u8, is_lucky: bool) {
|
// fn myFunction(number: u8, is_lucky: bool) void {
|
||||||
// ...
|
// ...
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -14,14 +14,19 @@ const std = @import("std");
|
|||||||
// You can find more information at:
|
// You can find more information at:
|
||||||
// https://ziglang.org/documentation/master/#Inferred-Error-Sets
|
// https://ziglang.org/documentation/master/#Inferred-Error-Sets
|
||||||
//
|
//
|
||||||
pub fn main() !void {
|
pub fn main(init: std.process.Init) !void {
|
||||||
// We get a Writer for Standard Out so we can print() to it.
|
// Instance for input/output operations; we will learn more about this later.
|
||||||
var stdout = std.fs.File.stdout().writer(&.{});
|
const io = init.io;
|
||||||
|
|
||||||
|
// We get a Writer for Standard Out...
|
||||||
|
var stdout_writer = std.Io.File.stdout().writer(io, &.{});
|
||||||
|
// ...and extract its interface so we can print() to it.
|
||||||
|
const stdout = &stdout_writer.interface;
|
||||||
|
|
||||||
// Unlike std.debug.print(), the Standard Out writer can fail
|
// Unlike std.debug.print(), the Standard Out writer can fail
|
||||||
// with an error. We don't care _what_ the error is, we want
|
// with an error. We don't care _what_ the error is, we want
|
||||||
// to be able to pass it up as a return value of main().
|
// to be able to pass it up as a return value of main().
|
||||||
//
|
//
|
||||||
// We just learned of a single statement which can accomplish this.
|
// We just learned of a single statement which can accomplish this.
|
||||||
stdout.interface.print("Hello world!\n", .{});
|
stdout.print("Hello world!\n", .{});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,14 @@ const std = @import("std");
|
|||||||
|
|
||||||
const NumError = error{IllegalNumber};
|
const NumError = error{IllegalNumber};
|
||||||
|
|
||||||
pub fn main() void {
|
pub fn main(init: std.process.Init) !void {
|
||||||
var stdout = std.fs.File.stdout().writer(&.{});
|
const io = init.io;
|
||||||
|
var stdout_writer = std.Io.File.stdout().writer(io, &.{});
|
||||||
|
const stdout = &stdout_writer.interface;
|
||||||
|
|
||||||
const my_num: u32 = getNumber();
|
const my_num: u32 = getNumber();
|
||||||
|
|
||||||
try stdout.interface.print("my_num={}\n", .{my_num});
|
try stdout.print("my_num={}\n", .{my_num});
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function is obviously weird and non-functional. But you will not be changing it for this quiz.
|
// This function is obviously weird and non-functional. But you will not be changing it for this quiz.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// var foo: u8 = 5; // foo is 5
|
// var foo: u8 = 5; // foo is 5
|
||||||
// var bar: *u8 = &foo; // bar is a pointer
|
// var bar: *u8 = &foo; // bar is a pointer
|
||||||
//
|
//
|
||||||
// What is a pointer? It's a reference to a value. In this example
|
// What is a pointer? It's a reference to a value. In this example,
|
||||||
// bar is a reference to the memory space that currently contains the
|
// bar is a reference to the memory space that currently contains the
|
||||||
// value 5.
|
// value 5.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -14,8 +14,8 @@
|
|||||||
//
|
//
|
||||||
// "undefined" should not be thought of as a value, but as a way
|
// "undefined" should not be thought of as a value, but as a way
|
||||||
// of telling the compiler that you are not assigning a value
|
// of telling the compiler that you are not assigning a value
|
||||||
// _yet_. Any type may be set to undefined, but attempting
|
// _yet_. Any variable may be set to undefined, but attempting to
|
||||||
// to read or use that value is _always_ a mistake.
|
// read its value before assigning one is _always_ a mistake.
|
||||||
//
|
//
|
||||||
// * null
|
// * null
|
||||||
//
|
//
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
// The "null" primitive value _is_ a value that means "no value".
|
// The "null" primitive value _is_ a value that means "no value".
|
||||||
// This is typically used with optional types as with the ?u8
|
// This is typically used with optional types as with the ?u8
|
||||||
// shown above. When foo equals null, that's not a value of type
|
// shown above. When foo equals null, that's not a value of type
|
||||||
// u8. It means there is _no value_ of type u8 in foo at all!
|
// u8. It means you have assigned foo to have _no value_!
|
||||||
//
|
//
|
||||||
// * error
|
// * error
|
||||||
//
|
//
|
||||||
@@ -32,10 +32,9 @@
|
|||||||
//
|
//
|
||||||
// Errors are _very_ similar to nulls. They _are_ a value, but
|
// Errors are _very_ similar to nulls. They _are_ a value, but
|
||||||
// they usually indicate that the "real value" you were looking
|
// they usually indicate that the "real value" you were looking
|
||||||
// for does not exist. Instead, you have an error. The example
|
// for does not exist. Instead of "no value", you have an error.
|
||||||
// error union type of MyError!u8 means that foo either holds
|
// The example error union type of MyError!u8 means that foo
|
||||||
// a u8 value OR an error. There is _no value_ of type u8 in foo
|
// either holds a u8 value OR a MyError error.
|
||||||
// when it's set to an error!
|
|
||||||
//
|
//
|
||||||
// * void
|
// * void
|
||||||
//
|
//
|
||||||
@@ -55,7 +54,7 @@
|
|||||||
// * undefined - there is no value YET, this cannot be read YET
|
// * undefined - there is no value YET, this cannot be read YET
|
||||||
// * null - there is an explicit value of "no value"
|
// * null - there is an explicit value of "no value"
|
||||||
// * errors - there is no value because something went wrong
|
// * errors - there is no value because something went wrong
|
||||||
// * void - there will NEVER be a value stored here
|
// * void - there will NEVER be a value here
|
||||||
//
|
//
|
||||||
// Please use the correct "no value" for each ??? to make this program
|
// Please use the correct "no value" for each ??? to make this program
|
||||||
// print out a cursed quote from the Necronomicon. ...If you dare.
|
// print out a cursed quote from the Necronomicon. ...If you dare.
|
||||||
|
|||||||
@@ -45,10 +45,13 @@ pub fn main() void {
|
|||||||
// conversion of 0.453592 kg to the pound.
|
// conversion of 0.453592 kg to the pound.
|
||||||
const shuttle_weight: f16 = 0.453592 * 4480e3;
|
const shuttle_weight: f16 = 0.453592 * 4480e3;
|
||||||
|
|
||||||
// By default, float values are formatted in scientific
|
// By default, float values are formatted in standard decimal
|
||||||
// notation. Try experimenting with '{d}' and '{d:.3}' to see
|
// notation. Experiment with '{d}' and '{d:.3}' to see how
|
||||||
// how decimal formatting works.
|
// decimal formatting works, or try '{e}' and '{e:.3}' for
|
||||||
print("Shuttle liftoff weight: {d:.0} metric tons\n", .{shuttle_weight});
|
// scientific notation.
|
||||||
|
// NOTE: The weight of the shuttle is a huge number, a scientific notation
|
||||||
|
// may be more appropriate.
|
||||||
|
print("Shuttle liftoff weight: {d:.0} metric tons\n", .{shuttle_weight / 1e3});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Floating further:
|
// Floating further:
|
||||||
|
|||||||
@@ -137,19 +137,20 @@ pub fn main() void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: This exercise did not originally include the function below.
|
// NOTE: This exercise did not originally include the function below.
|
||||||
// But a change after Zig 0.10.0 added the source file name to the
|
// After Zig 0.10.0, `@typeName` began prefixing the returned type name
|
||||||
// type. "Narcissus" became "065_builtins2.Narcissus".
|
// with the source file name. For example, "Narcissus" became
|
||||||
|
// "065_builtins2.Narcissus".
|
||||||
//
|
//
|
||||||
// To fix this, we've added this function to strip the filename from
|
// To fix this, we've added this function to strip the filename from
|
||||||
// the front of the type name. (It returns a slice of the type name
|
// the front of the type name. (It returns a slice of the type name
|
||||||
// starting at the index + 1 of character ".")
|
// starting just after the ".")
|
||||||
//
|
//
|
||||||
// We'll be seeing @typeName again in Exercise 070. For now, you can
|
// We'll be seeing @typeName again in Exercise 070. For now, you can
|
||||||
// see that it takes a Type and returns a u8 "string".
|
// see that it takes a Type and returns a u8 "string".
|
||||||
fn maximumNarcissism(myType: anytype) []const u8 {
|
fn maximumNarcissism(myType: type) []const u8 {
|
||||||
const indexOf = @import("std").mem.indexOf;
|
const find = @import("std").mem.find;
|
||||||
|
|
||||||
// Turn "065_builtins2.Narcissus" into "Narcissus"
|
// Turn "065_builtins2.Narcissus" into "Narcissus"
|
||||||
const name = @typeName(myType);
|
const name = @typeName(myType);
|
||||||
return name[indexOf(u8, name, ".").? + 1 ..];
|
return name[find(u8, name, ".").? + 1 ..];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
// --o-- comptime * | .. .
|
// --o-- comptime * | .. .
|
||||||
// * | * . . . . --*-- . * .
|
// * | * . . . . --*-- . * .
|
||||||
// . . . . . . . . . | . . .
|
// . . . . . . . . . | . . .
|
||||||
|
// (ASCII art depicting a starry sky with "comptime" as rising star)
|
||||||
//
|
//
|
||||||
// When placed before a variable declaration, 'comptime'
|
// When placed before a variable declaration, 'comptime'
|
||||||
// guarantees that every usage of that variable will be performed
|
// guarantees that every usage of that variable will be performed
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
// format string can be checked for errors at compile time rather
|
// format string can be checked for errors at compile time rather
|
||||||
// than crashing at runtime.
|
// than crashing at runtime.
|
||||||
//
|
//
|
||||||
// (The actual formatting is done by std.fmt.format() and it
|
// (The actual formatting is done by std.Io.Writer.print() and it
|
||||||
// contains a complete format string parser that runs entirely at
|
// contains a complete format string parser that runs entirely at
|
||||||
// compile time!)
|
// compile time!)
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
// wouldn't be allowed:
|
// wouldn't be allowed:
|
||||||
//
|
//
|
||||||
// inline for (.{ u8, u16, u32, u64 }) |T| {
|
// inline for (.{ u8, u16, u32, u64 }) |T| {
|
||||||
// print("{} ", .{@typeInfo(T).Int.bits});
|
// print("{} ", .{@typeInfo(T).int.bits});
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// In the above example, we're looping over a list of types,
|
// In the above example, we're looping over a list of types,
|
||||||
|
|||||||
@@ -1,62 +1,111 @@
|
|||||||
//
|
|
||||||
// In addition to knowing when to use the 'comptime' keyword,
|
|
||||||
// it's also good to know when you DON'T need it.
|
|
||||||
//
|
|
||||||
// The following contexts are already IMPLICITLY evaluated at
|
|
||||||
// compile time, and adding the 'comptime' keyword would be
|
|
||||||
// superfluous, redundant, and smelly:
|
|
||||||
//
|
|
||||||
// * The container-level scope (outside of any function in a source file)
|
|
||||||
// * Type declarations of:
|
|
||||||
// * Variables
|
|
||||||
// * Functions (types of parameters and return values)
|
|
||||||
// * Structs
|
|
||||||
// * Unions
|
|
||||||
// * Enums
|
|
||||||
// * The test expressions in inline for and while loops
|
|
||||||
// * An expression passed to the @cImport() builtin
|
|
||||||
//
|
|
||||||
// Work with Zig for a while, and you'll start to develop an
|
|
||||||
// intuition for these contexts. Let's work on that now.
|
|
||||||
//
|
|
||||||
// You have been given just one 'comptime' statement to use in
|
|
||||||
// the program below. Here it is:
|
|
||||||
//
|
|
||||||
// comptime
|
|
||||||
//
|
|
||||||
// Just one is all it takes. Use it wisely!
|
|
||||||
//
|
|
||||||
const print = @import("std").debug.print;
|
const print = @import("std").debug.print;
|
||||||
|
|
||||||
// Being in the container-level scope, everything about this value is
|
// We're going to (ab)use the power of Zig to make animal hybrid creatures!
|
||||||
// implicitly required to be known compile time.
|
// What do you think a GatorMouse would look like? Eek.
|
||||||
const llama_count = 5;
|
//
|
||||||
|
// Let's try a MouseLlama instead.
|
||||||
|
//
|
||||||
|
// We'll make a function that runs at comptime and takes a short code describing
|
||||||
|
// the desired creature. A Mouse is represented by "m" and a Llama is "lm".
|
||||||
|
// A MouseLlama hybrid, then, would be represented by "mlm".
|
||||||
|
|
||||||
// Again, this value's type and size must be known at compile
|
const Animal = enum {
|
||||||
// time, but we're letting the compiler infer both from the
|
Mouse,
|
||||||
// return type of a function.
|
Llama,
|
||||||
const llamas = makeLlamas(llama_count);
|
Gator,
|
||||||
|
};
|
||||||
|
|
||||||
// And here's the function. Note that the return value type
|
// makeCreature takes the count of animals making up the hybrid creature (so we
|
||||||
// depends on one of the input arguments!
|
// know how big a pen we'll need) and a format string, like the "mlm" for
|
||||||
fn makeLlamas(count: usize) [count]u8 {
|
// MouseLlama.
|
||||||
var temp: [count]u8 = undefined;
|
fn makeCreature(comptime count: usize, comptime fmt: []const u8) [count]Animal {
|
||||||
var i = 0;
|
|
||||||
|
|
||||||
// Note that this does NOT need to be an inline 'while'.
|
// Since not every animal is represented by a single character, we need to
|
||||||
while (i < count) : (i += 1) {
|
// track the state of things as we move along. For example, if we see an
|
||||||
temp[i] = i;
|
// "m", is that a new Mouse or the end of a Llama?
|
||||||
|
const State = enum {
|
||||||
|
start, // Ready to start a new animal.
|
||||||
|
l, // This means we've seen an "l", so if we see an "m", we know it's a Llama.
|
||||||
|
};
|
||||||
|
var state = State.start;
|
||||||
|
|
||||||
|
// We return an array of animals representing the creature. (This is why we
|
||||||
|
// really needed the 'count' parameter. Arrays need a size.)
|
||||||
|
var animals: [count]Animal = .{undefined} ** count;
|
||||||
|
var next_animal: usize = 0;
|
||||||
|
|
||||||
|
inline for (fmt) |char| {
|
||||||
|
|
||||||
|
// This is a good spot to add a @compileLog() call if you need to debug
|
||||||
|
// any variables... (Come back here after you see main().)
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
.start => switch (char) {
|
||||||
|
// We've seen the start of a Llama.
|
||||||
|
'l' => state = .l,
|
||||||
|
|
||||||
|
// Mice are smaller. An "m" is a full Mouse.
|
||||||
|
'm' => {
|
||||||
|
animals[next_animal] = .Mouse;
|
||||||
|
next_animal += 1;
|
||||||
|
},
|
||||||
|
|
||||||
|
// @compileError lets us stop the build immediately if something
|
||||||
|
// is wrong. It's like @compileLog but it prints a message
|
||||||
|
// instead of inspecting values.
|
||||||
|
//
|
||||||
|
// What do you think happens with Gators? Do they join with
|
||||||
|
// other animals or is this an error?
|
||||||
|
'g' => ???,
|
||||||
|
|
||||||
|
else => @compileError("No animal starts with '" ++ char ++ "'!"),
|
||||||
|
},
|
||||||
|
|
||||||
|
.l => switch (char) {
|
||||||
|
// We've seen the end of a Llama.
|
||||||
|
'm' => {
|
||||||
|
animals[next_animal] = .Llama;
|
||||||
|
next_animal += 1;
|
||||||
|
// Something is missing here. After we finish a Llama, we
|
||||||
|
// need to be ready to _start_ over with a new animal...
|
||||||
|
???
|
||||||
|
},
|
||||||
|
|
||||||
|
else => @compileError("Only llamas start with 'l'!"),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return temp;
|
if (state != .start) {
|
||||||
|
@compileError("Oh no, an incomplete llama!");
|
||||||
|
}
|
||||||
|
if (next_animal != count) {
|
||||||
|
@compileError("Creature is missing an animal (format string too short).");
|
||||||
|
}
|
||||||
|
|
||||||
|
return animals;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() void {
|
pub fn main() void {
|
||||||
print("My llama value is {}.\n", .{llamas[2]});
|
// Once you've fixed the ??? marks above, this makeCreature call will still
|
||||||
|
// only succeed if you move it outside of main, so it will run at comptime.
|
||||||
|
//
|
||||||
|
// With the call here, Zig will try to make the creature at runtime, and
|
||||||
|
// you'll get an interesting error.
|
||||||
|
//
|
||||||
|
// You may think the state got mixed up, but if you use @compileLog to check
|
||||||
|
// some variables in makeCreature, you'll see that Zig is trying to compare
|
||||||
|
// comptime values with "[runtime value]", which will never match.
|
||||||
|
//
|
||||||
|
// You can solve this by adding "comptime" to two of the variables in
|
||||||
|
// makeCreature...
|
||||||
|
const creature = makeCreature(2, "mlm");
|
||||||
|
|
||||||
|
for (creature) |animal| {
|
||||||
|
// @tagName gives us a string representing which variant of an enum we
|
||||||
|
// have. This lets us print the names of animals without repeating them
|
||||||
|
// here.
|
||||||
|
print("{s}", .{@tagName(animal)});
|
||||||
|
}
|
||||||
|
print(" joins the crew!", .{});
|
||||||
}
|
}
|
||||||
//
|
|
||||||
// The lesson here is to not pepper your program with 'comptime'
|
|
||||||
// keywords unless you need them. Between the implicit compile
|
|
||||||
// time contexts and Zig's aggressive evaluation of any
|
|
||||||
// expression it can figure out at compile time, it's sometimes
|
|
||||||
// surprising how few places actually need the keyword.
|
|
||||||
|
|||||||
@@ -118,6 +118,10 @@ fn printTuple(tuple: anytype) void {
|
|||||||
// @field(foo, "x"); // returns the value at foo.x
|
// @field(foo, "x"); // returns the value at foo.x
|
||||||
//
|
//
|
||||||
// The first field should print as: "0"(bool):true
|
// The first field should print as: "0"(bool):true
|
||||||
|
//
|
||||||
|
// Hint: Be careful! If your 'lhs' is a type, @field() looks
|
||||||
|
// for declarations. If it's a value, it looks for data.
|
||||||
|
//
|
||||||
print("\"{s}\"({any}):{any} ", .{
|
print("\"{s}\"({any}):{any} ", .{
|
||||||
field.???,
|
field.???,
|
||||||
field.???,
|
field.???,
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
//
|
|
||||||
// Six Facts:
|
|
||||||
//
|
|
||||||
// 1. The memory space allocated to your program for the
|
|
||||||
// invocation of a function and all of its data is called a
|
|
||||||
// "stack frame".
|
|
||||||
//
|
|
||||||
// 2. The 'return' keyword "pops" the current function
|
|
||||||
// invocation's frame off of the stack (it is no longer needed)
|
|
||||||
// and returns control to the place where the function was
|
|
||||||
// called.
|
|
||||||
//
|
|
||||||
// fn foo() void {
|
|
||||||
// return; // Pop the frame and return control
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// 3. Like 'return', the 'suspend' keyword returns control to the
|
|
||||||
// place where the function was called BUT the function
|
|
||||||
// invocation's frame remains so that it can regain control again
|
|
||||||
// at a later time. Functions which do this are "async"
|
|
||||||
// functions.
|
|
||||||
//
|
|
||||||
// fn fooThatSuspends() void {
|
|
||||||
// suspend {} // return control, but leave the frame alone
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// 4. To call any function in async context and get a reference
|
|
||||||
// to its frame for later use, use the 'async' keyword:
|
|
||||||
//
|
|
||||||
// var foo_frame = async fooThatSuspends();
|
|
||||||
//
|
|
||||||
// 5. If you call an async function without the 'async' keyword,
|
|
||||||
// the function FROM WHICH you called the async function itself
|
|
||||||
// becomes async! In this example, the bar() function is now
|
|
||||||
// async because it calls fooThatSuspends(), which is async.
|
|
||||||
//
|
|
||||||
// fn bar() void {
|
|
||||||
// fooThatSuspends();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// 6. The main() function cannot be async!
|
|
||||||
//
|
|
||||||
// Given facts 3 and 4, how do we fix this program (broken by facts
|
|
||||||
// 5 and 6)?
|
|
||||||
//
|
|
||||||
const print = @import("std").debug.print;
|
|
||||||
|
|
||||||
pub fn main() void {
|
|
||||||
// Additional Hint: you can assign things to '_' when you
|
|
||||||
// don't intend to do anything with them.
|
|
||||||
foo();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn foo() void {
|
|
||||||
print("foo() A\n", .{});
|
|
||||||
suspend {}
|
|
||||||
print("foo() B\n", .{});
|
|
||||||
}
|
|
||||||
@@ -102,7 +102,7 @@ pub fn main() !void {
|
|||||||
Insect{ .grasshopper = Grasshopper{ .distance_hopped = 32 } },
|
Insect{ .grasshopper = Grasshopper{ .distance_hopped = 32 } },
|
||||||
};
|
};
|
||||||
|
|
||||||
std.debug.print("Daily Insect Report:\n", .{});
|
std.debug.print("=== Doctor Zoraptera's Insect Report ===\n", .{});
|
||||||
for (my_insects) |insect| {
|
for (my_insects) |insect| {
|
||||||
// Almost done! We want to print() each insect with a
|
// Almost done! We want to print() each insect with a
|
||||||
// single method call here.
|
// single method call here.
|
||||||
49
exercises/085_async.zig
Normal file
49
exercises/085_async.zig
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
//
|
||||||
|
// In previous versions of Zig, async/await used special keywords
|
||||||
|
// like 'suspend', 'resume', and 'async' that operated on stackframes
|
||||||
|
// directly. Those keywords no longer exist!
|
||||||
|
//
|
||||||
|
// Zig 0.16 replaced them with a unified I/O interface: std.Io.
|
||||||
|
// This interface uses a VTable pattern - a struct of function pointers -
|
||||||
|
// to abstract over different concurrency backends:
|
||||||
|
//
|
||||||
|
// * Threaded - thread-pool based I/O
|
||||||
|
// * Evented - chooses the best event-loop backend for your OS:
|
||||||
|
// * Uring on Linux (io_uring)
|
||||||
|
// * Kqueue on BSD/macOS
|
||||||
|
// * Dispatch on macOS (Grand Central Dispatch)
|
||||||
|
//
|
||||||
|
// The Io struct itself is tiny:
|
||||||
|
//
|
||||||
|
// const Io = struct {
|
||||||
|
// userdata: ?*anyopaque, // opaque state of the backend
|
||||||
|
// vtable: *const VTable, // table of function pointers
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// Your code receives an Io value and calls methods on it.
|
||||||
|
// The backend is chosen at initialization time - your code doesn't
|
||||||
|
// need to know which one it is!
|
||||||
|
//
|
||||||
|
// In Zig 0.16, main() receives a std.process.Init struct to opt
|
||||||
|
// into I/O and concurrency support:
|
||||||
|
//
|
||||||
|
// pub fn main(init: std.process.Init) !void {
|
||||||
|
// const io = init.io;
|
||||||
|
// // ... use io ...
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Let's start simple. Fix the main function to extract the Io
|
||||||
|
// interface from init, then use it to get the current time.
|
||||||
|
//
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn main(init: std.process.Init) !void {
|
||||||
|
const io = init.???;
|
||||||
|
|
||||||
|
// Get the current wall-clock time using the Io interface.
|
||||||
|
// Hint: Timestamp.now() takes an Io and a Clock type (.real = wall clock).
|
||||||
|
const timestamp = std.Io.Timestamp.now(io, .real);
|
||||||
|
|
||||||
|
// Print the timestamp in seconds since the Unix epoch.
|
||||||
|
std.debug.print("Current time: {}s since epoch\n", .{timestamp.toSeconds()});
|
||||||
|
}
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
//
|
|
||||||
// So, 'suspend' returns control to the place from which it was
|
|
||||||
// called (the "call site"). How do we give control back to the
|
|
||||||
// suspended function?
|
|
||||||
//
|
|
||||||
// For that, we have a new keyword called 'resume' which takes an
|
|
||||||
// async function invocation's frame and returns control to it.
|
|
||||||
//
|
|
||||||
// fn fooThatSuspends() void {
|
|
||||||
// suspend {}
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// var foo_frame = async fooThatSuspends();
|
|
||||||
// resume foo_frame;
|
|
||||||
//
|
|
||||||
// See if you can make this program print "Hello async!".
|
|
||||||
//
|
|
||||||
const print = @import("std").debug.print;
|
|
||||||
|
|
||||||
pub fn main() void {
|
|
||||||
var foo_frame = async foo();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn foo() void {
|
|
||||||
print("Hello ", .{});
|
|
||||||
suspend {}
|
|
||||||
print("async!\n", .{});
|
|
||||||
}
|
|
||||||
54
exercises/086_async2.zig
Normal file
54
exercises/086_async2.zig
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
//
|
||||||
|
// Now that we know how to get an Io value, let's use it for
|
||||||
|
// asynchronous execution!
|
||||||
|
//
|
||||||
|
// io.async() launches a function and returns a Future. The result
|
||||||
|
// won't necessarily be available until you call .await() on it:
|
||||||
|
//
|
||||||
|
// var future = io.async(someFunction, .{ arg1, arg2 });
|
||||||
|
// const result = future.await(io);
|
||||||
|
//
|
||||||
|
// The function *may* run immediately or on another thread -
|
||||||
|
// your code doesn't need to care! That's the beauty of the
|
||||||
|
// Io abstraction.
|
||||||
|
//
|
||||||
|
// IMPORTANT: Every Future MUST be either .await()ed or .cancel()ed.
|
||||||
|
// Failing to do so leaks resources! A safe pattern is:
|
||||||
|
//
|
||||||
|
// var future = io.async(myFn, .{});
|
||||||
|
// defer _ = future.cancel(io); // safety net
|
||||||
|
// // ... later, if we want the result:
|
||||||
|
// const result = future.await(io);
|
||||||
|
// // (await after cancel is fine — it just returns the result)
|
||||||
|
//
|
||||||
|
// Both .await() and .cancel() block until the task finishes and
|
||||||
|
// return the result. The difference is that .cancel() also
|
||||||
|
// requests the task to stop at its next cancellation point.
|
||||||
|
// Calling either one more than once is safe — subsequent calls
|
||||||
|
// just return a copy of the result.
|
||||||
|
//
|
||||||
|
// Fix this program so that computeAnswer runs asynchronously
|
||||||
|
// and its result is properly awaited.
|
||||||
|
//
|
||||||
|
const std = @import("std");
|
||||||
|
const print = std.debug.print;
|
||||||
|
|
||||||
|
pub fn main(init: std.process.Init) !void {
|
||||||
|
const io = init.io;
|
||||||
|
|
||||||
|
// Launch computeAnswer asynchronously.
|
||||||
|
var future = io.async(computeAnswer, .{ 6, 7 });
|
||||||
|
defer _ = future.cancel(io); // always clean up!
|
||||||
|
|
||||||
|
print("Computing... ", .{});
|
||||||
|
|
||||||
|
// Now collect the result. What method on Future gives us
|
||||||
|
// the value, blocking until it's ready?
|
||||||
|
const answer = future.???(io);
|
||||||
|
|
||||||
|
print("The answer is: {}\n", .{answer});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn computeAnswer(a: u32, b: u32) u32 {
|
||||||
|
return a * b;
|
||||||
|
}
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
//
|
|
||||||
// Because they can suspend and resume, async Zig functions are
|
|
||||||
// an example of a more general programming concept called
|
|
||||||
// "coroutines". One of the neat things about Zig async functions
|
|
||||||
// is that they retain their state as they are suspended and
|
|
||||||
// resumed.
|
|
||||||
//
|
|
||||||
// See if you can make this program print "5 4 3 2 1".
|
|
||||||
//
|
|
||||||
const print = @import("std").debug.print;
|
|
||||||
|
|
||||||
pub fn main() void {
|
|
||||||
const n = 5;
|
|
||||||
var foo_frame = async foo(n);
|
|
||||||
|
|
||||||
???
|
|
||||||
|
|
||||||
print("\n", .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn foo(countdown: u32) void {
|
|
||||||
var current = countdown;
|
|
||||||
|
|
||||||
while (current > 0) {
|
|
||||||
print("{} ", .{current});
|
|
||||||
current -= 1;
|
|
||||||
suspend {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
49
exercises/087_async3.zig
Normal file
49
exercises/087_async3.zig
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
//
|
||||||
|
// The real power of async shows when you launch MULTIPLE tasks!
|
||||||
|
//
|
||||||
|
// With io.async(), you can start several operations, then await
|
||||||
|
// them all. The Io backend may run them concurrently:
|
||||||
|
//
|
||||||
|
// var f1 = io.async(taskA, .{});
|
||||||
|
// defer _ = f1.cancel(io);
|
||||||
|
// var f2 = io.async(taskB, .{});
|
||||||
|
// defer _ = f2.cancel(io);
|
||||||
|
// const a = f1.await(io);
|
||||||
|
// const b = f2.await(io);
|
||||||
|
//
|
||||||
|
// Notice the defer pattern: each async call is immediately
|
||||||
|
// followed by a defer cancel. This ensures cleanup even if
|
||||||
|
// we return early or hit an error before reaching await.
|
||||||
|
// Since await/cancel are idempotent, the defer is harmless
|
||||||
|
// if we've already awaited.
|
||||||
|
//
|
||||||
|
// Fix this program to launch both tasks and collect their results.
|
||||||
|
//
|
||||||
|
const std = @import("std");
|
||||||
|
const print = std.debug.print;
|
||||||
|
|
||||||
|
pub fn main(init: std.process.Init) !void {
|
||||||
|
const io = init.io;
|
||||||
|
|
||||||
|
// Launch both tasks asynchronously.
|
||||||
|
var future_a = io.async(slowAdd, .{ 1, 2 });
|
||||||
|
defer _ = future_a.cancel(io);
|
||||||
|
var future_b = ???(slowMul, .{ 6, 7 });
|
||||||
|
defer _ = future_b.cancel(io);
|
||||||
|
|
||||||
|
// Await both results.
|
||||||
|
const sum = future_a.await(io);
|
||||||
|
const product = future_b.await(io);
|
||||||
|
|
||||||
|
print("{} + {} = {}\n", .{ 1, 2, sum });
|
||||||
|
print("{} * {} = {}\n", .{ 6, 7, product });
|
||||||
|
print("Total: {}\n", .{sum + product});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn slowAdd(a: u32, b: u32) u32 {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn slowMul(a: u32, b: u32) u32 {
|
||||||
|
return a * b;
|
||||||
|
}
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
//
|
|
||||||
// It has probably not escaped your attention that we are no
|
|
||||||
// longer capturing a return value from foo() because the 'async'
|
|
||||||
// keyword returns the frame instead.
|
|
||||||
//
|
|
||||||
// One way to solve this is to use a global variable.
|
|
||||||
//
|
|
||||||
// See if you can make this program print "1 2 3 4 5".
|
|
||||||
//
|
|
||||||
const print = @import("std").debug.print;
|
|
||||||
|
|
||||||
var global_counter: i32 = 0;
|
|
||||||
|
|
||||||
pub fn main() void {
|
|
||||||
var foo_frame = async foo();
|
|
||||||
|
|
||||||
while (global_counter <= 5) {
|
|
||||||
print("{} ", .{global_counter});
|
|
||||||
???
|
|
||||||
}
|
|
||||||
|
|
||||||
print("\n", .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn foo() void {
|
|
||||||
while (true) {
|
|
||||||
???
|
|
||||||
???
|
|
||||||
}
|
|
||||||
}
|
|
||||||
50
exercises/088_async4.zig
Normal file
50
exercises/088_async4.zig
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
//
|
||||||
|
// When you have many tasks that don't return individual values,
|
||||||
|
// use a Group! A Group is an unordered set of tasks that can
|
||||||
|
// only be awaited or canceled as a whole:
|
||||||
|
//
|
||||||
|
// var group: std.Io.Group = .init;
|
||||||
|
// group.async(io, myTask, .{arg1});
|
||||||
|
// group.async(io, myTask, .{arg2});
|
||||||
|
// try group.await(io); // blocks until ALL tasks finish
|
||||||
|
//
|
||||||
|
// Important rules:
|
||||||
|
// * The return type of functions spawned in a group must be
|
||||||
|
// coercible to Cancelable!void (i.e. void, or error{Canceled}!void).
|
||||||
|
// * Once you call group.async(), you MUST eventually call
|
||||||
|
// group.await() or group.cancel() to release resources.
|
||||||
|
// * group.cancel() requests cancellation on ALL members,
|
||||||
|
// then blocks until they all finish.
|
||||||
|
//
|
||||||
|
// Unlike Future, Group tasks don't return values to the caller.
|
||||||
|
// They're ideal for parallel work that communicates through
|
||||||
|
// shared state or side effects (like printing).
|
||||||
|
//
|
||||||
|
// Fix this program to await all tasks in the group.
|
||||||
|
//
|
||||||
|
const std = @import("std");
|
||||||
|
const print = std.debug.print;
|
||||||
|
|
||||||
|
pub fn main(init: std.process.Init) !void {
|
||||||
|
const io = init.io;
|
||||||
|
|
||||||
|
var group: std.Io.Group = .init;
|
||||||
|
|
||||||
|
// Spawn 3 tasks in any order. Each sleeps for (id * 1) seconds
|
||||||
|
// before printing, so the output order is deterministic.
|
||||||
|
group.async(io, doWork, .{ io, 1 });
|
||||||
|
group.async(io, doWork, .{ io, 3 });
|
||||||
|
group.async(io, doWork, .{ io, 2 });
|
||||||
|
|
||||||
|
// Wait for all tasks to finish.
|
||||||
|
// What Group method blocks until all tasks complete?
|
||||||
|
try group.???(io);
|
||||||
|
|
||||||
|
print("All tasks finished!\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn doWork(io: std.Io, id: u32) void {
|
||||||
|
// Sleep ensures deterministic output order.
|
||||||
|
io.sleep(std.Io.Duration.fromSeconds(id), .awake) catch return;
|
||||||
|
print("Task {} done.\n", .{id});
|
||||||
|
}
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
//
|
|
||||||
// Sure, we can solve our async value problem with a global
|
|
||||||
// variable. But this hardly seems like an ideal solution.
|
|
||||||
//
|
|
||||||
// So how do we REALLY get return values from async functions?
|
|
||||||
//
|
|
||||||
// The 'await' keyword waits for an async function to complete
|
|
||||||
// and then captures its return value.
|
|
||||||
//
|
|
||||||
// fn foo() u32 {
|
|
||||||
// return 5;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// var foo_frame = async foo(); // invoke and get frame
|
|
||||||
// var value = await foo_frame; // await result using frame
|
|
||||||
//
|
|
||||||
// The above example is just a silly way to call foo() and get 5
|
|
||||||
// back. But if foo() did something more interesting such as wait
|
|
||||||
// for a network response to get that 5, our code would pause
|
|
||||||
// until the value was ready.
|
|
||||||
//
|
|
||||||
// As you can see, async/await basically splits a function call
|
|
||||||
// into two parts:
|
|
||||||
//
|
|
||||||
// 1. Invoke the function ('async')
|
|
||||||
// 2. Getting the return value ('await')
|
|
||||||
//
|
|
||||||
// Also notice that a 'suspend' keyword does NOT need to exist in
|
|
||||||
// a function to be called in an async context.
|
|
||||||
//
|
|
||||||
// Please use 'await' to get the string returned by
|
|
||||||
// getPageTitle().
|
|
||||||
//
|
|
||||||
const print = @import("std").debug.print;
|
|
||||||
|
|
||||||
pub fn main() void {
|
|
||||||
var myframe = async getPageTitle("http://example.com");
|
|
||||||
|
|
||||||
var value = ???
|
|
||||||
|
|
||||||
print("{s}\n", .{value});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn getPageTitle(url: []const u8) []const u8 {
|
|
||||||
// Please PRETEND this is actually making a network request.
|
|
||||||
_ = url;
|
|
||||||
return "Example Title.";
|
|
||||||
}
|
|
||||||
67
exercises/089_async5.zig
Normal file
67
exercises/089_async5.zig
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
//
|
||||||
|
// One of the most important features of the new Io system is
|
||||||
|
// structured cancellation!
|
||||||
|
//
|
||||||
|
// Every Future has a .cancel() method that:
|
||||||
|
// 1. Requests the task to stop (via error.Canceled at the
|
||||||
|
// next "cancellation point")
|
||||||
|
// 2. BLOCKS until the task actually finishes
|
||||||
|
// 3. Returns whatever result the task produced
|
||||||
|
//
|
||||||
|
// A "cancellation point" is any Io function that can return
|
||||||
|
// error.Canceled - most commonly io.sleep():
|
||||||
|
//
|
||||||
|
// fn myTask(io: std.Io) u32 {
|
||||||
|
// io.sleep(...) catch |err| switch (err) {
|
||||||
|
// error.Canceled => return 0, // error handle
|
||||||
|
// };
|
||||||
|
// return 42;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// This is fundamentally different from killing a thread -
|
||||||
|
// the task gets a chance to clean up and return a value!
|
||||||
|
//
|
||||||
|
// Remember: both .await() and .cancel() block and return the
|
||||||
|
// result. The only difference is that .cancel() also sends
|
||||||
|
// the cancellation request. And both are idempotent — calling
|
||||||
|
// either one again just returns the same result.
|
||||||
|
//
|
||||||
|
// Fix this program: the slow task would take 10 seconds,
|
||||||
|
// but we cancel it after 1 second. The task should detect
|
||||||
|
// the cancellation and return early.
|
||||||
|
//
|
||||||
|
const std = @import("std");
|
||||||
|
const print = std.debug.print;
|
||||||
|
|
||||||
|
pub fn main(init: std.process.Init) !void {
|
||||||
|
const io = init.io;
|
||||||
|
|
||||||
|
var future = io.async(slowTask, .{io});
|
||||||
|
defer _ = future.cancel(io); // safety net
|
||||||
|
|
||||||
|
// Wait 1 second, then cancel instead of waiting the full 10.
|
||||||
|
io.sleep(std.Io.Duration.fromSeconds(1), .awake) catch {};
|
||||||
|
|
||||||
|
print("Canceling slow task...\n", .{});
|
||||||
|
|
||||||
|
// We don't want to wait 10 seconds!
|
||||||
|
// Which Future method requests cancellation AND returns the result?
|
||||||
|
const result = future.???(io);
|
||||||
|
|
||||||
|
print("Task returned: {}\n", .{result});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn slowTask(io: std.Io) u32 {
|
||||||
|
print("Starting long computation...\n", .{});
|
||||||
|
|
||||||
|
// Try to sleep for 10 seconds - but we might get canceled!
|
||||||
|
io.sleep(std.Io.Duration.fromSeconds(10), .awake) catch |err| switch (err) {
|
||||||
|
error.Canceled => {
|
||||||
|
print("Task was canceled, cleaning up.\n", .{});
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
print("Task completed normally.\n", .{});
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
//
|
|
||||||
// The power and purpose of async/await becomes more apparent
|
|
||||||
// when we do multiple things concurrently. Foo and Bar do not
|
|
||||||
// depend on each other and can happen at the same time, but End
|
|
||||||
// requires that they both be finished.
|
|
||||||
//
|
|
||||||
// +---------+
|
|
||||||
// | Start |
|
|
||||||
// +---------+
|
|
||||||
// / \
|
|
||||||
// / \
|
|
||||||
// +---------+ +---------+
|
|
||||||
// | Foo | | Bar |
|
|
||||||
// +---------+ +---------+
|
|
||||||
// \ /
|
|
||||||
// \ /
|
|
||||||
// +---------+
|
|
||||||
// | End |
|
|
||||||
// +---------+
|
|
||||||
//
|
|
||||||
// We can express this in Zig like so:
|
|
||||||
//
|
|
||||||
// fn foo() u32 { ... }
|
|
||||||
// fn bar() u32 { ... }
|
|
||||||
//
|
|
||||||
// // Start
|
|
||||||
//
|
|
||||||
// var foo_frame = async foo();
|
|
||||||
// var bar_frame = async bar();
|
|
||||||
//
|
|
||||||
// var foo_value = await foo_frame;
|
|
||||||
// var bar_value = await bar_frame;
|
|
||||||
//
|
|
||||||
// // End
|
|
||||||
//
|
|
||||||
// Please await TWO page titles!
|
|
||||||
//
|
|
||||||
const print = @import("std").debug.print;
|
|
||||||
|
|
||||||
pub fn main() void {
|
|
||||||
var com_frame = async getPageTitle("http://example.com");
|
|
||||||
var org_frame = async getPageTitle("http://example.org");
|
|
||||||
|
|
||||||
var com_title = com_frame;
|
|
||||||
var org_title = org_frame;
|
|
||||||
|
|
||||||
print(".com: {s}, .org: {s}.\n", .{ com_title, org_title });
|
|
||||||
}
|
|
||||||
|
|
||||||
fn getPageTitle(url: []const u8) []const u8 {
|
|
||||||
// Please PRETEND this is actually making a network request.
|
|
||||||
_ = url;
|
|
||||||
return "Example Title";
|
|
||||||
}
|
|
||||||
76
exercises/090_async6.zig
Normal file
76
exercises/090_async6.zig
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
//
|
||||||
|
// Sometimes you want to race multiple tasks and act on whichever
|
||||||
|
// finishes first. That's what Select is for!
|
||||||
|
//
|
||||||
|
// Select is like a Group, but lets you receive individual results
|
||||||
|
// as tasks complete — one at a time:
|
||||||
|
//
|
||||||
|
// const Race = std.Io.Select(union(enum) {
|
||||||
|
// fast: u32,
|
||||||
|
// slow: u32,
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// var buffer: [2]Race.Union = undefined;
|
||||||
|
// var sel = Race.init(io, &buffer);
|
||||||
|
//
|
||||||
|
// sel.async(.fast, fastFn, .{io});
|
||||||
|
// sel.async(.slow, slowFn, .{io});
|
||||||
|
//
|
||||||
|
// const winner = try sel.await(); // returns first completed
|
||||||
|
// switch (winner) {
|
||||||
|
// .fast => |val| ...,
|
||||||
|
// .slow => |val| ...,
|
||||||
|
// }
|
||||||
|
// sel.cancelDiscard(); // cancel remaining, discard results
|
||||||
|
//
|
||||||
|
// As with all async primitives: tasks spawned in a Select MUST
|
||||||
|
// be cleaned up. Use sel.cancel() to get remaining results one
|
||||||
|
// by one (for resource cleanup), or sel.cancelDiscard() if you
|
||||||
|
// don't need them.
|
||||||
|
//
|
||||||
|
// The buffer must be large enough for all tasks that might
|
||||||
|
// complete before you call cancelDiscard().
|
||||||
|
//
|
||||||
|
// Fix this program to receive the winner of the race.
|
||||||
|
//
|
||||||
|
const std = @import("std");
|
||||||
|
const print = std.debug.print;
|
||||||
|
|
||||||
|
const RaceResult = std.Io.Select(union(enum) {
|
||||||
|
hare: []const u8,
|
||||||
|
tortoise: []const u8,
|
||||||
|
});
|
||||||
|
|
||||||
|
pub fn main(init: std.process.Init) !void {
|
||||||
|
const io = init.io;
|
||||||
|
|
||||||
|
var buffer: [2]RaceResult.Union = undefined;
|
||||||
|
var sel = RaceResult.init(io, &buffer);
|
||||||
|
|
||||||
|
sel.async(.hare, runHare, .{io});
|
||||||
|
sel.async(.tortoise, runTortoise, .{io});
|
||||||
|
|
||||||
|
// Wait for the first finisher.
|
||||||
|
// What Select method returns the first completed result?
|
||||||
|
const winner = try sel.???();
|
||||||
|
|
||||||
|
switch (winner) {
|
||||||
|
.hare => |msg| print("Hare: {s}\n", .{msg}),
|
||||||
|
.tortoise => |msg| print("Tortoise: {s}\n", .{msg}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up the loser — we don't need their result.
|
||||||
|
sel.cancelDiscard();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn runHare(io: std.Io) []const u8 {
|
||||||
|
// The hare is fast — only 1 second!
|
||||||
|
io.sleep(std.Io.Duration.fromSeconds(1), .awake) catch return "I got canceled!";
|
||||||
|
return "I'm fast!";
|
||||||
|
}
|
||||||
|
|
||||||
|
fn runTortoise(io: std.Io) []const u8 {
|
||||||
|
// The tortoise is slow — 10 seconds.
|
||||||
|
io.sleep(std.Io.Duration.fromSeconds(10), .awake) catch return "I got canceled!";
|
||||||
|
return "Slow and steady...";
|
||||||
|
}
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
//
|
|
||||||
// Remember how a function with 'suspend' is async and calling an
|
|
||||||
// async function without the 'async' keyword makes the CALLING
|
|
||||||
// function async?
|
|
||||||
//
|
|
||||||
// fn fooThatMightSuspend(maybe: bool) void {
|
|
||||||
// if (maybe) suspend {}
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// fn bar() void {
|
|
||||||
// fooThatMightSuspend(true); // Now bar() is async!
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// But if you KNOW the function won't suspend, you can make a
|
|
||||||
// promise to the compiler with the 'nosuspend' keyword:
|
|
||||||
//
|
|
||||||
// fn bar() void {
|
|
||||||
// nosuspend fooThatMightSuspend(false);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// If the function does suspend and YOUR PROMISE TO THE COMPILER
|
|
||||||
// IS BROKEN, the program will panic at runtime, which is
|
|
||||||
// probably better than you deserve, you oathbreaker! >:-(
|
|
||||||
//
|
|
||||||
const print = @import("std").debug.print;
|
|
||||||
|
|
||||||
pub fn main() void {
|
|
||||||
|
|
||||||
// The main() function can not be async. But we know
|
|
||||||
// getBeef() will not suspend with this particular
|
|
||||||
// invocation. Please make this okay:
|
|
||||||
var my_beef = getBeef(0);
|
|
||||||
|
|
||||||
print("beef? {X}!\n", .{my_beef});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn getBeef(input: u32) u32 {
|
|
||||||
if (input == 0xDEAD) {
|
|
||||||
suspend {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0xBEEF;
|
|
||||||
}
|
|
||||||
//
|
|
||||||
// Going Deeper Into...
|
|
||||||
// ...uNdeFiNEd beHAVi0r!
|
|
||||||
//
|
|
||||||
// We haven't discussed it yet, but runtime "safety" features
|
|
||||||
// require some extra instructions in your compiled program.
|
|
||||||
// Most of the time, you're going to want to keep these in.
|
|
||||||
//
|
|
||||||
// But in some programs, when data integrity is less important
|
|
||||||
// than raw speed (some games, for example), you can compile
|
|
||||||
// without these safety features.
|
|
||||||
//
|
|
||||||
// Instead of a safe panic when something goes wrong, your
|
|
||||||
// program will now exhibit Undefined Behavior (UB), which simply
|
|
||||||
// means that the Zig language does not (cannot) define what will
|
|
||||||
// happen. The best case is that it will crash, but in the worst
|
|
||||||
// case, it will continue to run with the wrong results and
|
|
||||||
// corrupt your data or expose you to security risks.
|
|
||||||
//
|
|
||||||
// This program is a great way to explore UB. Once you get it
|
|
||||||
// working, try calling the getBeef() function with the value
|
|
||||||
// 0xDEAD so that it will invoke the 'suspend' keyword:
|
|
||||||
//
|
|
||||||
// getBeef(0xDEAD)
|
|
||||||
//
|
|
||||||
// Now when you run the program, it will panic and give you a
|
|
||||||
// nice stack trace to help debug the problem.
|
|
||||||
//
|
|
||||||
// zig run exercises/090_async7.zig
|
|
||||||
// thread 328 panic: async function called...
|
|
||||||
// ...
|
|
||||||
//
|
|
||||||
// But see what happens when you turn off safety checks by using
|
|
||||||
// ReleaseFast mode:
|
|
||||||
//
|
|
||||||
// zig run -O ReleaseFast exercises/090_async7.zig
|
|
||||||
// beef? 0!
|
|
||||||
//
|
|
||||||
// This is the wrong result. On your computer, you may get a
|
|
||||||
// different answer or it might crash! What exactly will happen
|
|
||||||
// is UNDEFINED. Your computer is now like a wild animal,
|
|
||||||
// reacting to bits and bytes of raw memory with the base
|
|
||||||
// instincts of the CPU. It is both terrifying and exhilarating.
|
|
||||||
//
|
|
||||||
57
exercises/091_async7.zig
Normal file
57
exercises/091_async7.zig
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
//
|
||||||
|
// When multiple async tasks access shared data, you need
|
||||||
|
// synchronization! Io provides a Mutex for this:
|
||||||
|
//
|
||||||
|
// var mutex: std.Io.Mutex = .init;
|
||||||
|
//
|
||||||
|
// // In a task:
|
||||||
|
// try mutex.lock(io); // blocks until lock is acquired
|
||||||
|
// defer mutex.unlock();
|
||||||
|
// // ... critical section: safe to modify shared data ...
|
||||||
|
//
|
||||||
|
// Without the mutex, concurrent tasks could read and write the
|
||||||
|
// same memory simultaneously, causing a data race — the result
|
||||||
|
// would be unpredictable.
|
||||||
|
//
|
||||||
|
// mutex.lock() is a cancellation point — it can return
|
||||||
|
// error.Canceled. There's also tryLock() which returns
|
||||||
|
// immediately (true if acquired, false if not).
|
||||||
|
//
|
||||||
|
// Fix this program so the counter is correctly synchronized.
|
||||||
|
// Without the fix, the final count would be unpredictable.
|
||||||
|
// With it, four tasks incrementing 100 times each = 400.
|
||||||
|
//
|
||||||
|
const std = @import("std");
|
||||||
|
const print = std.debug.print;
|
||||||
|
|
||||||
|
const SharedState = struct {
|
||||||
|
counter: u32 = 0,
|
||||||
|
mutex: std.Io.Mutex = .init,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn main(init: std.process.Init) !void {
|
||||||
|
const io = init.io;
|
||||||
|
var state = SharedState{};
|
||||||
|
|
||||||
|
var group: std.Io.Group = .init;
|
||||||
|
|
||||||
|
group.async(io, increment, .{ io, &state, 100 });
|
||||||
|
group.async(io, increment, .{ io, &state, 100 });
|
||||||
|
group.async(io, increment, .{ io, &state, 100 });
|
||||||
|
group.async(io, increment, .{ io, &state, 100 });
|
||||||
|
|
||||||
|
try group.await(io);
|
||||||
|
|
||||||
|
print("Counter: {}\n", .{state.counter});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn increment(io: std.Io, state: *SharedState, times: u32) void {
|
||||||
|
for (0..times) |_| {
|
||||||
|
// Acquire the lock before modifying shared state.
|
||||||
|
// What Mutex method blocks until the lock is acquired?
|
||||||
|
state.mutex.??? catch return;
|
||||||
|
defer state.mutex.unlock(); // <-- what's missing here?
|
||||||
|
|
||||||
|
state.counter += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
//
|
|
||||||
// You have doubtless noticed that 'suspend' requires a block
|
|
||||||
// expression like so:
|
|
||||||
//
|
|
||||||
// suspend {}
|
|
||||||
//
|
|
||||||
// The suspend block executes when a function suspends. To get
|
|
||||||
// sense for when this happens, please make the following
|
|
||||||
// program print the string
|
|
||||||
//
|
|
||||||
// "ABCDEF"
|
|
||||||
//
|
|
||||||
const print = @import("std").debug.print;
|
|
||||||
|
|
||||||
pub fn main() void {
|
|
||||||
print("A", .{});
|
|
||||||
|
|
||||||
var frame = async suspendable();
|
|
||||||
|
|
||||||
print("X", .{});
|
|
||||||
|
|
||||||
resume frame;
|
|
||||||
|
|
||||||
print("F", .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn suspendable() void {
|
|
||||||
print("X", .{});
|
|
||||||
|
|
||||||
suspend {
|
|
||||||
print("X", .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
print("X", .{});
|
|
||||||
}
|
|
||||||
62
exercises/092_async8.zig
Normal file
62
exercises/092_async8.zig
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
//
|
||||||
|
// Tasks often need to communicate! Io provides Queue for this —
|
||||||
|
// a bounded, thread-safe channel for passing data between tasks:
|
||||||
|
//
|
||||||
|
// var backing: [16]u32 = undefined;
|
||||||
|
// var queue: std.Io.Queue(u32) = .init(&backing);
|
||||||
|
//
|
||||||
|
// // Producer task:
|
||||||
|
// try queue.putOne(io, value); // blocks if queue is full
|
||||||
|
//
|
||||||
|
// // Consumer task:
|
||||||
|
// const val = try queue.getOne(io); // blocks if queue is empty
|
||||||
|
//
|
||||||
|
// When the producer is done, it calls queue.close(io) to signal
|
||||||
|
// that no more data is coming. After that, getOne() will return
|
||||||
|
// error.Closed once the queue is drained.
|
||||||
|
//
|
||||||
|
// This is the classic producer/consumer pattern — one task
|
||||||
|
// generates work, another processes it, and the queue handles
|
||||||
|
// all the synchronization automatically.
|
||||||
|
//
|
||||||
|
// Fix this program: the producer sends numbers 1..10, the
|
||||||
|
// consumer sums them up. The expected sum is 55.
|
||||||
|
//
|
||||||
|
const std = @import("std");
|
||||||
|
const print = std.debug.print;
|
||||||
|
|
||||||
|
pub fn main(init: std.process.Init) !void {
|
||||||
|
const io = init.io;
|
||||||
|
|
||||||
|
var backing: [4]u32 = undefined;
|
||||||
|
var queue: std.Io.Queue(u32) = .init(&backing);
|
||||||
|
|
||||||
|
var group: std.Io.Group = .init;
|
||||||
|
|
||||||
|
group.async(io, producer, .{ io, &queue });
|
||||||
|
group.async(io, consumer, .{ io, &queue });
|
||||||
|
|
||||||
|
try group.await(io);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn producer(io: std.Io, queue: *std.Io.Queue(u32)) void {
|
||||||
|
// Send numbers 1 through 10 into the queue.
|
||||||
|
for (1..11) |i| {
|
||||||
|
// What Queue method sends a single element, blocking if full?
|
||||||
|
queue.???(io, @intCast(i)) catch return;
|
||||||
|
}
|
||||||
|
// Signal that we're done sending.
|
||||||
|
queue.close(io);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn consumer(io: std.Io, queue: *std.Io.Queue(u32)) void {
|
||||||
|
var sum: u32 = 0;
|
||||||
|
while (true) {
|
||||||
|
const value = queue.getOne(io) catch |err| switch (err) {
|
||||||
|
error.Closed => break,
|
||||||
|
error.Canceled => return,
|
||||||
|
};
|
||||||
|
sum += value;
|
||||||
|
}
|
||||||
|
print("Sum of 1..10 = {}\n", .{sum});
|
||||||
|
}
|
||||||
109
exercises/093_async9.zig
Normal file
109
exercises/093_async9.zig
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
//
|
||||||
|
// We've been using io.async() to launch tasks. But there's a
|
||||||
|
// stronger variant: io.concurrent().
|
||||||
|
//
|
||||||
|
// The difference:
|
||||||
|
//
|
||||||
|
// io.async():
|
||||||
|
// * The function MAY run on a separate unit of concurrency,
|
||||||
|
// or it may run immediately on the caller (synchronously).
|
||||||
|
// * Never fails — if no concurrency is available, it just
|
||||||
|
// runs the function right away.
|
||||||
|
// * More portable, works with all Io backends.
|
||||||
|
//
|
||||||
|
// io.concurrent():
|
||||||
|
// * GUARANTEES a separate unit of concurrency.
|
||||||
|
// * Can fail with error.ConcurrencyUnavailable if resources
|
||||||
|
// are exhausted or the backend doesn't support it.
|
||||||
|
// * Use when you NEED the task to run independently of the
|
||||||
|
// caller.
|
||||||
|
//
|
||||||
|
// What is a "unit of concurrency"? That depends on the backend!
|
||||||
|
// The Threaded backend uses OS threads. But the Evented backends
|
||||||
|
// (Uring, Kqueue, Dispatch) use M:N green threads / fibers,
|
||||||
|
// which can provide concurrency even on a SINGLE OS thread.
|
||||||
|
// Your code doesn't need to know the difference.
|
||||||
|
//
|
||||||
|
// Because concurrent() can fail, you must handle the error:
|
||||||
|
//
|
||||||
|
// var future = try io.concurrent(myFn, .{args});
|
||||||
|
// defer _ = future.cancel(io);
|
||||||
|
// const result = future.await(io);
|
||||||
|
//
|
||||||
|
// Let's try a slightly simplified example from signal processing:
|
||||||
|
// Suppose we're looking for the beginning of a signal above the noise
|
||||||
|
// level. To do this, we compare each entry from beginning to end with
|
||||||
|
// the threshold. To speed things up a bit, we split the signal into
|
||||||
|
// two halves and have two parallel workers search for them.
|
||||||
|
// Who finds the beginning first "wins" and thus ends the other one.
|
||||||
|
//
|
||||||
|
// As I said, this is a simplified explanation,
|
||||||
|
// but in practice it's done more or less like this.
|
||||||
|
//
|
||||||
|
const std = @import("std");
|
||||||
|
const Io = std.Io;
|
||||||
|
const print = std.debug.print;
|
||||||
|
|
||||||
|
const SearchResult = struct {
|
||||||
|
found: bool,
|
||||||
|
worker_id: u8 = 0,
|
||||||
|
index: usize = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn main(init: std.process.Init) !void {
|
||||||
|
const io = init.io;
|
||||||
|
|
||||||
|
const data = [_]u32{ 10, 23, 45, 67, 12, 69, 3, 54, 69, 42, 68, 56, 71, 79, 79, 75, 70, 77 };
|
||||||
|
const threshold = 70;
|
||||||
|
const mid = data.len / 2;
|
||||||
|
|
||||||
|
// A queue with space for one result.
|
||||||
|
var buf: [1]SearchResult = undefined;
|
||||||
|
var queue = Io.Queue(SearchResult).init(&buf);
|
||||||
|
|
||||||
|
// Launch two workers, each searching half the array.
|
||||||
|
// Remember, we want them to be guaranteed separate units of concurrency.
|
||||||
|
var f1 = ???(searchThreshold, .{ io, data[0..mid], threshold, 0, 0, &queue });
|
||||||
|
defer _ = f1.cancel(io);
|
||||||
|
|
||||||
|
var f2 = ???(searchThreshold, .{ io, data[mid..], threshold, mid, 1, &queue });
|
||||||
|
defer _ = f2.cancel(io);
|
||||||
|
|
||||||
|
// Wait for the first result.
|
||||||
|
const result = try queue.getOne(io);
|
||||||
|
|
||||||
|
if (result.found)
|
||||||
|
print("Worker {} found signal start over threshold at index {}!\n", .{ result.worker_id, result.index });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn searchThreshold(
|
||||||
|
io: Io,
|
||||||
|
slice: []const u32,
|
||||||
|
threshold: u32,
|
||||||
|
base_offset: usize,
|
||||||
|
worker_id: u8,
|
||||||
|
queue: *Io.Queue(SearchResult),
|
||||||
|
) void {
|
||||||
|
for (slice, 0..) |val, i| {
|
||||||
|
// This pause is necessary so that the process can be canceled
|
||||||
|
// if another one has already finished. Without this pause,
|
||||||
|
// all workers would continue until the end.
|
||||||
|
io.sleep(Io.Duration.fromMilliseconds(1), .awake) catch return;
|
||||||
|
|
||||||
|
// To test this, you can uncomment this to view the work of the workers
|
||||||
|
// and then comment out the pause.
|
||||||
|
// print("id: {} - val: {}\n", .{ worker_id, val });
|
||||||
|
|
||||||
|
if (val >= threshold) {
|
||||||
|
queue.putOne(io, .{
|
||||||
|
.found = true,
|
||||||
|
.worker_id = worker_id,
|
||||||
|
.index = base_offset + i,
|
||||||
|
}) catch return;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing found
|
||||||
|
queue.putOneUncancelable(io, .{ .found = false }) catch return;
|
||||||
|
}
|
||||||
68
exercises/094_async10.zig
Normal file
68
exercises/094_async10.zig
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
//
|
||||||
|
// In exercise 089, we learned that cancellation happens at
|
||||||
|
// "cancellation points" — any Io function that can return
|
||||||
|
// error.Canceled.
|
||||||
|
//
|
||||||
|
// But sometimes a task has a critical section that MUST NOT
|
||||||
|
// be interrupted — for example, writing a consistent state
|
||||||
|
// to disk, or completing a transaction.
|
||||||
|
//
|
||||||
|
// Io provides CancelProtection for this:
|
||||||
|
//
|
||||||
|
// const old = io.swapCancelProtection(.blocked);
|
||||||
|
// defer _ = io.swapCancelProtection(old);
|
||||||
|
|
||||||
|
// // In this block, NO Io function will return error.Canceled.
|
||||||
|
// // The cancel request is held until protection is restored.
|
||||||
|
//
|
||||||
|
// There are two states:
|
||||||
|
// .unblocked — normal: cancellation points can fire (default)
|
||||||
|
// .blocked — protected: error.Canceled is never returned
|
||||||
|
//
|
||||||
|
// There's also io.checkCancel() — a pure cancellation point
|
||||||
|
// that does nothing except return error.Canceled if a cancel
|
||||||
|
// request is pending. Useful in long CPU-bound loops.
|
||||||
|
//
|
||||||
|
// And io.recancel() — re-arms a consumed cancel request so
|
||||||
|
// the NEXT cancellation point will fire again.
|
||||||
|
//
|
||||||
|
// Fix this program so the critical section completes even
|
||||||
|
// when the task is canceled.
|
||||||
|
//
|
||||||
|
const std = @import("std");
|
||||||
|
const print = std.debug.print;
|
||||||
|
|
||||||
|
pub fn main(init: std.process.Init) !void {
|
||||||
|
const io = init.io;
|
||||||
|
|
||||||
|
var future = io.async(importantTask, .{io});
|
||||||
|
defer _ = future.cancel(io);
|
||||||
|
|
||||||
|
// Give the task time to start and enter its critical section.
|
||||||
|
io.sleep(std.Io.Duration.fromMilliseconds(200), .awake) catch {};
|
||||||
|
|
||||||
|
// Cancel while the task is in its protected section.
|
||||||
|
const result = future.cancel(io);
|
||||||
|
print("Task result: {s}\n", .{result});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn importantTask(io: std.Io) []const u8 {
|
||||||
|
print("Starting critical section...\n", .{});
|
||||||
|
|
||||||
|
// Protect this section from cancellation.
|
||||||
|
// What method swaps the cancel protection state?
|
||||||
|
const old = io.???(.blocked);
|
||||||
|
defer _ = io.???(old);
|
||||||
|
|
||||||
|
// This sleep will NOT return error.Canceled even though
|
||||||
|
// we get canceled during it — protection is active!
|
||||||
|
io.sleep(std.Io.Duration.fromMilliseconds(300), .awake) catch |err| switch (err) {
|
||||||
|
error.Canceled => {
|
||||||
|
// This should never happen while protected!
|
||||||
|
return "ERROR: canceled during critical section!";
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
print("Critical section completed safely.\n", .{});
|
||||||
|
return "All data saved.";
|
||||||
|
}
|
||||||
187
exercises/095_quiz_async.zig
Normal file
187
exercises/095_quiz_async.zig
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
//
|
||||||
|
// Quiz Time — Async I/O!
|
||||||
|
//
|
||||||
|
// Doctor Zoraptera's insect simulation is going well, but she
|
||||||
|
// realized that her virtual garden needs weather data! Insects
|
||||||
|
// behave differently depending on temperature, humidity, and
|
||||||
|
// wind conditions.
|
||||||
|
//
|
||||||
|
// She has set up three weather sensors around the garden that
|
||||||
|
// measure conditions in parallel and report their readings
|
||||||
|
// through a shared data channel. A collector task gathers the
|
||||||
|
// readings, and after all sensors have reported, a garden
|
||||||
|
// report is printed.
|
||||||
|
//
|
||||||
|
// But Doctor Z rushed through the code (she was being chased
|
||||||
|
// by a grasshopper) and left several bugs. Can you fix them?
|
||||||
|
//
|
||||||
|
// Here's what the program should do:
|
||||||
|
// 1. Three sensor tasks send exactly 3 readings each through
|
||||||
|
// a Queue
|
||||||
|
// 2. A collector task receives readings concurrently,
|
||||||
|
// protected by a Mutex
|
||||||
|
// 3. After all sensors finish, the queue is closed
|
||||||
|
// 4. The final report is written in a cancel-protected section
|
||||||
|
//
|
||||||
|
// *************************************************************
|
||||||
|
// * A NOTE ABOUT THIS EXERCISE *
|
||||||
|
// * *
|
||||||
|
// * This quiz uses concepts from exercises 085-094. *
|
||||||
|
// * There are 6 bugs to fix — look for the ???s! *
|
||||||
|
// * *
|
||||||
|
// *************************************************************
|
||||||
|
//
|
||||||
|
const std = @import("std");
|
||||||
|
const print = std.debug.print;
|
||||||
|
|
||||||
|
const SensorType = enum { thermometer, hygrometer, anemometer };
|
||||||
|
|
||||||
|
const Reading = struct {
|
||||||
|
sensor_type: SensorType,
|
||||||
|
value: i32,
|
||||||
|
};
|
||||||
|
|
||||||
|
const GardenWeather = struct {
|
||||||
|
temperature: i32 = 0,
|
||||||
|
humidity: i32 = 0,
|
||||||
|
wind: i32 = 0,
|
||||||
|
readings_count: u32 = 0,
|
||||||
|
mutex: std.Io.Mutex = .init,
|
||||||
|
|
||||||
|
fn addReading(self: *GardenWeather, io: std.Io, reading: Reading) void {
|
||||||
|
// Bug 1: The collector needs to lock before modifying
|
||||||
|
// shared state. What Mutex method acquires the lock?
|
||||||
|
self.mutex.???(io) catch return;
|
||||||
|
defer self.mutex.unlock(io);
|
||||||
|
|
||||||
|
switch (reading.sensor_type) {
|
||||||
|
.thermometer => self.temperature = reading.value,
|
||||||
|
.hygrometer => self.humidity = reading.value,
|
||||||
|
.anemometer => self.wind = reading.value,
|
||||||
|
}
|
||||||
|
self.readings_count += 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn main(init: std.process.Init) !void {
|
||||||
|
const io = init.io;
|
||||||
|
|
||||||
|
var weather = GardenWeather{};
|
||||||
|
|
||||||
|
var reading_buf: [8]Reading = undefined;
|
||||||
|
var queue: std.Io.Queue(Reading) = .init(&reading_buf);
|
||||||
|
|
||||||
|
// The collector must run concurrently so it can process
|
||||||
|
// readings while the sensors are still sending.
|
||||||
|
// Start it FIRST to ensure its concurrency unit is reserved.
|
||||||
|
//
|
||||||
|
// Bug 2: The collector needs guaranteed concurrency.
|
||||||
|
// What method ensures a separate unit of concurrency?
|
||||||
|
// (Don't forget: it can fail!)
|
||||||
|
var collector_future = try io.???(collector, .{ io, &queue, &weather });
|
||||||
|
defer _ = collector_future.cancel(io);
|
||||||
|
|
||||||
|
// Sensor group: the sensors can use async — they just need
|
||||||
|
// to run, and async is more portable.
|
||||||
|
var sensors: std.Io.Group = .init;
|
||||||
|
|
||||||
|
sensors.async(io, sensor, .{ io, &queue, .thermometer, 20 });
|
||||||
|
sensors.async(io, sensor, .{ io, &queue, .hygrometer, 60 });
|
||||||
|
sensors.async(io, sensor, .{ io, &queue, .anemometer, 10 });
|
||||||
|
|
||||||
|
// Bug 3: Wait for ALL sensors to finish sending their readings.
|
||||||
|
// What Group method blocks until all tasks complete?
|
||||||
|
try sensors.???(io);
|
||||||
|
|
||||||
|
// All sensors done — close the queue so the collector knows
|
||||||
|
// there's no more data coming.
|
||||||
|
queue.close(io);
|
||||||
|
|
||||||
|
// Bug 4: How do we wait for the collector to drain the remaining queue?
|
||||||
|
_ = collector_future.???(io);
|
||||||
|
|
||||||
|
// Now write the garden report. This is critical — it must
|
||||||
|
// NOT be interrupted, even if something tries to cancel us!
|
||||||
|
//
|
||||||
|
// Bug 5: Protect this section from cancellation.
|
||||||
|
// What Io method swaps the cancel protection state?
|
||||||
|
const old_protection = io.???(.blocked);
|
||||||
|
defer _ = io.???(old_protection);
|
||||||
|
|
||||||
|
printGardenReport(&weather);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sensor(
|
||||||
|
io: std.Io,
|
||||||
|
queue: *std.Io.Queue(Reading),
|
||||||
|
sensor_type: SensorType,
|
||||||
|
base_value: i32,
|
||||||
|
) void {
|
||||||
|
// Each sensor takes exactly 3 measurements.
|
||||||
|
for (1..4) |i| {
|
||||||
|
io.sleep(std.Io.Duration.fromMilliseconds(100), .awake) catch return;
|
||||||
|
|
||||||
|
const reading = Reading{
|
||||||
|
.sensor_type = sensor_type,
|
||||||
|
.value = base_value + @as(i32, @intCast(i)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Bug 6: Send the reading into the queue.
|
||||||
|
// What Queue method sends a single element?
|
||||||
|
queue.???(io, reading) catch return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collector(
|
||||||
|
io: std.Io,
|
||||||
|
queue: *std.Io.Queue(Reading),
|
||||||
|
weather: *GardenWeather,
|
||||||
|
) void {
|
||||||
|
while (true) {
|
||||||
|
const reading = queue.getOne(io) catch |err| switch (err) {
|
||||||
|
error.Closed => break,
|
||||||
|
error.Canceled => return,
|
||||||
|
};
|
||||||
|
weather.addReading(io, reading);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn printGardenReport(weather: *GardenWeather) void {
|
||||||
|
print("=== Doctor Zoraptera's Garden Report ===\n", .{});
|
||||||
|
print("Temperature : {}C\n", .{weather.temperature});
|
||||||
|
print("Humidity : {}%\n", .{weather.humidity});
|
||||||
|
print("Wind : {} km/h\n", .{weather.wind});
|
||||||
|
print("Readings : {}\n", .{weather.readings_count});
|
||||||
|
|
||||||
|
if (weather.temperature > 20 and weather.wind < 15) {
|
||||||
|
print("Bee-friendly conditions! Expect high pollination.\n", .{});
|
||||||
|
} else {
|
||||||
|
print("Grasshoppers will be grumpy today.\n", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Further reading for the curious:
|
||||||
|
//
|
||||||
|
// This quiz covered the main async I/O primitives:
|
||||||
|
// io.async() - launch a task (may run inline)
|
||||||
|
// io.concurrent() - guaranteed unit of concurrency
|
||||||
|
// Future.await/cancel - collect or cancel a single task
|
||||||
|
// Group.async/await/cancel - manage fire-and-forget tasks
|
||||||
|
// Select.async/await - race tasks, act on first completion
|
||||||
|
// Queue - bounded channel between tasks
|
||||||
|
// Mutex - protect shared state
|
||||||
|
// CancelProtection - shield critical sections
|
||||||
|
//
|
||||||
|
// There are more synchronization primitives we didn't cover:
|
||||||
|
// Condition - wait for a condition to become true
|
||||||
|
// RwLock - multiple readers OR one writer
|
||||||
|
// Semaphore - limit concurrent access to a resource
|
||||||
|
// Futex - low-level wait/wake on a memory address
|
||||||
|
// Batch - submit multiple I/O operations at once
|
||||||
|
//
|
||||||
|
// The key insight: all of these work through the Io VTable,
|
||||||
|
// so your code is portable across backends — whether Threaded
|
||||||
|
// (OS thread pool), or Evented (M:N green threads / fibers
|
||||||
|
// that can provide concurrency even on a single OS thread).
|
||||||
|
//
|
||||||
|
// Doctor Zoraptera approves.
|
||||||
@@ -28,6 +28,8 @@
|
|||||||
// 0..10 is a range from 0 to 9
|
// 0..10 is a range from 0 to 9
|
||||||
// 1..4 is a range from 1 to 3
|
// 1..4 is a range from 1 to 3
|
||||||
//
|
//
|
||||||
|
// Crucially, the end value is EXCLUSIVE.
|
||||||
|
//
|
||||||
// At the moment, ranges in loops are only supported in 'for' loops.
|
// At the moment, ranges in loops are only supported in 'for' loops.
|
||||||
//
|
//
|
||||||
// Perhaps you recall Exercise 13? We were printing a numeric
|
// Perhaps you recall Exercise 13? We were printing a numeric
|
||||||
@@ -64,6 +66,12 @@ pub fn main() void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std.debug.print("\n", .{});
|
std.debug.print("\n", .{});
|
||||||
|
|
||||||
|
// Let's also print every number from 1 through 15
|
||||||
|
for (???) |n| {
|
||||||
|
std.debug.print("{} ", .{n});
|
||||||
|
}
|
||||||
|
std.debug.print("\n", .{});
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
// That's a bit nicer, right?
|
// That's a bit nicer, right?
|
||||||
@@ -13,10 +13,10 @@
|
|||||||
// no official documentation for standard library features such
|
// no official documentation for standard library features such
|
||||||
// as string formatting.
|
// as string formatting.
|
||||||
//
|
//
|
||||||
// Therefore, the comments for the format() function are the only
|
// Therefore, the comments for the print() function are the only
|
||||||
// way to definitively learn how to format strings in Zig:
|
// way to definitively learn how to format strings in Zig:
|
||||||
//
|
//
|
||||||
// https://github.com/ziglang/zig/blob/master/lib/std/fmt.zig#L33
|
// https://ziglang.org/documentation/master/std/#std.Io.Writer.print
|
||||||
//
|
//
|
||||||
// Zig already has a very nice selection of formatting options.
|
// Zig already has a very nice selection of formatting options.
|
||||||
// These can be used in different ways, but generally to convert
|
// These can be used in different ways, but generally to convert
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
// the placeholder will determine how the corresponding value,
|
// the placeholder will determine how the corresponding value,
|
||||||
// e.g. foo, is displayed.
|
// e.g. foo, is displayed.
|
||||||
//
|
//
|
||||||
// And this is where it gets exciting, because format() accepts a
|
// And this is where it gets exciting, because print() accepts a
|
||||||
// variety of formatting instructions. It's basically a tiny
|
// variety of formatting instructions. It's basically a tiny
|
||||||
// language of its own. Here's a numeric example:
|
// language of its own. Here's a numeric example:
|
||||||
//
|
//
|
||||||
@@ -37,63 +37,48 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
// This is a simple function
|
// This is a simple function that builds a sum from the passed parameters and
|
||||||
// that builds a sum from the
|
// returns.
|
||||||
// passed parameters and returns.
|
|
||||||
fn add(a: f16, b: f16) f16 {
|
fn add(a: f16, b: f16) f16 {
|
||||||
return a + b;
|
return a + b;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The associated test.
|
// The associated test. It always starts with the keyword "test", followed by a
|
||||||
// It always starts with the keyword "test",
|
// description of the tasks of the test. This is followed by the test cases in
|
||||||
// followed by a description of the tasks
|
// curly brackets.
|
||||||
// of the test. This is followed by the
|
|
||||||
// test cases in curly brackets.
|
|
||||||
test "add" {
|
test "add" {
|
||||||
|
|
||||||
// The first test checks if the sum
|
// The first test checks if the sum of '41' and '1' gives '42', which is
|
||||||
// of '41' and '1' gives '42', which
|
// correct.
|
||||||
// is correct.
|
|
||||||
try testing.expect(add(41, 1) == 42);
|
try testing.expect(add(41, 1) == 42);
|
||||||
|
|
||||||
// Another way to perform this test
|
// Another way to perform this test is as follows:
|
||||||
// is as follows:
|
try testing.expectEqual(42, add(41, 1));
|
||||||
try testing.expectEqual(add(41, 1), 42);
|
|
||||||
|
|
||||||
// This time a test with the addition
|
// This time a test with the addition of a negative number:
|
||||||
// of a negative number:
|
|
||||||
try testing.expect(add(5, -4) == 1);
|
try testing.expect(add(5, -4) == 1);
|
||||||
|
|
||||||
// And a floating point operation:
|
// And a floating point operation:
|
||||||
try testing.expect(add(1.5, 1.5) == 3);
|
try testing.expect(add(1.5, 1.5) == 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Another simple function
|
// Another simple function that returns the result of subtracting the two
|
||||||
// that returns the result
|
|
||||||
// of subtracting the two
|
|
||||||
// parameters.
|
// parameters.
|
||||||
fn sub(a: f16, b: f16) f16 {
|
fn sub(a: f16, b: f16) f16 {
|
||||||
return a - b;
|
return a - b;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The corresponding test
|
// The corresponding test is not much different from the previous one. Except
|
||||||
// is not much different
|
// that it contains an error that you need to correct.
|
||||||
// from the previous one.
|
|
||||||
// Except that it contains
|
|
||||||
// an error that you need
|
|
||||||
// to correct.
|
|
||||||
test "sub" {
|
test "sub" {
|
||||||
try testing.expect(sub(10, 5) == 6);
|
try testing.expect(sub(10, 5) == 6);
|
||||||
|
|
||||||
try testing.expect(sub(3, 1.5) == 1.5);
|
try testing.expect(sub(3, 1.5) == 1.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function divides the
|
// This function divides the numerator by the denominator. Here it is important
|
||||||
// numerator by the denominator.
|
// that the denominator must not be zero. This is checked and if it occurs an
|
||||||
// Here it is important that the
|
// error is returned.
|
||||||
// denominator must not be zero.
|
|
||||||
// This is checked and if it
|
|
||||||
// occurs an error is returned.
|
|
||||||
fn divide(a: f16, b: f16) !f16 {
|
fn divide(a: f16, b: f16) !f16 {
|
||||||
if (b == 0) return error.DivisionByZero;
|
if (b == 0) return error.DivisionByZero;
|
||||||
return a / b;
|
return a / b;
|
||||||
@@ -105,8 +90,7 @@ test "divide" {
|
|||||||
try testing.expect(divide(10, 2) catch unreachable == 5);
|
try testing.expect(divide(10, 2) catch unreachable == 5);
|
||||||
try testing.expect(divide(1, 3) catch unreachable == 0.3333333333333333);
|
try testing.expect(divide(1, 3) catch unreachable == 0.3333333333333333);
|
||||||
|
|
||||||
// Now we test if the function returns an error
|
// Now we test if the function returns an error if we pass a zero as
|
||||||
// if we pass a zero as denominator.
|
// denominator. But which error needs to be tested?
|
||||||
// But which error needs to be tested?
|
|
||||||
try testing.expectError(error.???, divide(15, 0));
|
try testing.expectError(error.???, divide(15, 0));
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
// The functionality of the standard library is becoming increasingly
|
// The functionality of the standard library is becoming increasingly
|
||||||
// important in Zig. First of all, it is helpful to take a look at how
|
// important in Zig. First of all, it is helpful to take a look at how
|
||||||
// the individual functions are implemented. Because this is wonderfully
|
// the individual functions are implemented. Because this is wonderfully
|
||||||
// suitable as a template for your own functions. In addition these
|
// suitable as a template for your own functions. In addition, these
|
||||||
// standard functions are part of the basic configuration of Zig.
|
// standard functions are part of the basic configuration of Zig.
|
||||||
//
|
//
|
||||||
// This means that they are always available on every system.
|
// This means that they are always available on every system.
|
||||||
@@ -24,8 +24,7 @@
|
|||||||
// suited to understand the basic principles.
|
// suited to understand the basic principles.
|
||||||
//
|
//
|
||||||
// In the following exercises we will also read and process data from
|
// In the following exercises we will also read and process data from
|
||||||
// large files and at the latest then it will be clear to everyone how
|
// large files, it will then be clearer to you how useful all this is.
|
||||||
// useful all this is.
|
|
||||||
//
|
//
|
||||||
// Let's start with the analysis of the example from the Zig homepage
|
// Let's start with the analysis of the example from the Zig homepage
|
||||||
// and explain the most important things.
|
// and explain the most important things.
|
||||||
@@ -48,15 +47,14 @@
|
|||||||
// // In order to be able to process the input values,
|
// // In order to be able to process the input values,
|
||||||
// // memory is required. An allocator is defined here for
|
// // memory is required. An allocator is defined here for
|
||||||
// // this purpose.
|
// // this purpose.
|
||||||
// const ally = std.testing.allocator;
|
// const gpa = std.testing.allocator;
|
||||||
//
|
//
|
||||||
// // The allocator is used to initialize an array into which
|
// // An array into which the numbers are stored is initialized.
|
||||||
// // the numbers are stored.
|
// var list: std.ArrayList(u32) = .empty;
|
||||||
// var list = std.ArrayList(u32).init(ally);
|
|
||||||
//
|
//
|
||||||
// // This way you can never forget what is urgently needed
|
// // This way you can never forget what is urgently needed
|
||||||
// // and the compiler doesn't grumble either.
|
// // and the compiler doesn't grumble either.
|
||||||
// defer list.deinit();
|
// defer list.deinit(gpa);
|
||||||
//
|
//
|
||||||
// // Now it gets exciting:
|
// // Now it gets exciting:
|
||||||
// // A standard tokenizer is called (Zig has several) and
|
// // A standard tokenizer is called (Zig has several) and
|
||||||
@@ -73,7 +71,7 @@
|
|||||||
// const n = try parseInt(u32, num, 10);
|
// const n = try parseInt(u32, num, 10);
|
||||||
//
|
//
|
||||||
// // Finally the individual values are stored in the array.
|
// // Finally the individual values are stored in the array.
|
||||||
// try list.append(n);
|
// try list.append(gpa, n);
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// // For the subsequent test, a second static array is created,
|
// // For the subsequent test, a second static array is created,
|
||||||
@@ -1,31 +1,22 @@
|
|||||||
//
|
//
|
||||||
// Whenever there is a lot to calculate, the question arises as to how
|
// In Exercises 84-91, we learned about Zig's Io interface for
|
||||||
// tasks can be carried out simultaneously. We have already learned about
|
// concurrent execution: io.async(), Group, Select, and Futures.
|
||||||
// one possibility, namely asynchronous processes, in Exercises 84-91.
|
// Under the hood, the Threaded backend manages a pool of real
|
||||||
|
// OS threads for you - including scheduling, cancellation, and
|
||||||
|
// resource cleanup.
|
||||||
//
|
//
|
||||||
// However, the computing power of the processor is only distributed to
|
// But sometimes you need direct control over threads:
|
||||||
// the started and running tasks, which always reaches its limits when
|
// * Long-lived dedicated workers
|
||||||
// pure computing power is called up.
|
// * Specific stack sizes or thread counts
|
||||||
|
// * Code that doesn't have an Io interface available
|
||||||
|
// * Fine-grained synchronization patterns
|
||||||
//
|
//
|
||||||
// For example, in blockchains based on proof of work, the miners have
|
// That's where std.Thread comes in. It gives you a raw OS thread
|
||||||
// to find a nonce for a certain character string so that the first m bits
|
// that you spawn, manage, and join yourself. No pool, no Futures,
|
||||||
// in the hash of the character string and the nonce are zeros.
|
// no automatic cancellation - but full control.
|
||||||
// As the miner who can solve the task first receives the reward, everyone
|
|
||||||
// tries to complete the calculations as quickly as possible.
|
|
||||||
//
|
//
|
||||||
// This is where multithreading comes into play, where tasks are actually
|
// The following diagram roughly illustrates the difference between
|
||||||
// distributed across several cores of the CPU or GPU, which then really
|
// the various types of process execution:
|
||||||
// means a multiplication of performance.
|
|
||||||
//
|
|
||||||
// The following diagram roughly illustrates the difference between the
|
|
||||||
// various types of process execution.
|
|
||||||
// The 'Overall Time' column is intended to illustrate how the time is
|
|
||||||
// affected if, instead of one core as in synchronous and asynchronous
|
|
||||||
// processing, a second core now helps to complete the work in multithreading.
|
|
||||||
//
|
|
||||||
// In the ideal case shown, execution takes only half the time compared
|
|
||||||
// to the synchronous single thread. And even asynchronous processing
|
|
||||||
// is only slightly faster in comparison.
|
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
// Synchronous Asynchronous
|
// Synchronous Asynchronous
|
||||||
@@ -106,7 +97,9 @@ pub fn main() !void {
|
|||||||
|
|
||||||
// After the threads have been started,
|
// After the threads have been started,
|
||||||
// they run in parallel and we can still do some work in between.
|
// they run in parallel and we can still do some work in between.
|
||||||
std.Thread.sleep(1500 * std.time.ns_per_ms);
|
var io_instance: std.Io.Threaded = .init_single_threaded;
|
||||||
|
const io = io_instance.io();
|
||||||
|
try io.sleep(std.Io.Duration.fromMilliseconds(400), .awake);
|
||||||
std.debug.print("Some weird stuff, after starting the threads.\n", .{});
|
std.debug.print("Some weird stuff, after starting the threads.\n", .{});
|
||||||
}
|
}
|
||||||
// After we have left the closed area, we wait until
|
// After we have left the closed area, we wait until
|
||||||
@@ -116,15 +109,17 @@ pub fn main() !void {
|
|||||||
|
|
||||||
// This function is started with every thread that we set up.
|
// This function is started with every thread that we set up.
|
||||||
// In our example, we pass the number of the thread as a parameter.
|
// In our example, we pass the number of the thread as a parameter.
|
||||||
fn thread_function(num: usize) !void {
|
fn thread_function(id: usize) !void {
|
||||||
std.Thread.sleep(200 * num * std.time.ns_per_ms);
|
var io_instance: std.Io.Threaded = .init_single_threaded;
|
||||||
std.debug.print("thread {d}: {s}\n", .{ num, "started." });
|
const io = io_instance.io();
|
||||||
|
try io.sleep(std.Io.Duration.fromMilliseconds(100 * @as(isize, @intCast(id))), .awake);
|
||||||
|
std.debug.print("thread {d}: {s}\n", .{ id, "started." });
|
||||||
|
|
||||||
// This timer simulates the work of the thread.
|
// This timer simulates the work of the thread.
|
||||||
const work_time = 3 * ((5 - num % 3) - 2);
|
const work_time = 300 * ((5 - id % 3) - 2);
|
||||||
std.Thread.sleep(work_time * std.time.ns_per_s);
|
try io.sleep(std.Io.Duration.fromMilliseconds(@intCast(work_time)), .awake);
|
||||||
|
|
||||||
std.debug.print("thread {d}: {s}\n", .{ num, "finished." });
|
std.debug.print("thread {d}: {s}\n", .{ id, "finished." });
|
||||||
}
|
}
|
||||||
// This is the easiest way to run threads in parallel.
|
// This is the easiest way to run threads in parallel.
|
||||||
// In general, however, more management effort is required,
|
// In general, however, more management effort is required,
|
||||||
@@ -105,3 +105,6 @@ fn thread_pi(pi: *f64, begin: u64, end: u64) !void {
|
|||||||
//
|
//
|
||||||
// And you should remove the formatting restriction in "print",
|
// And you should remove the formatting restriction in "print",
|
||||||
// otherwise you will not be able to see the additional digits.
|
// otherwise you will not be able to see the additional digits.
|
||||||
|
//
|
||||||
|
// If count = 10_000_000_000_000 you should see the following:
|
||||||
|
// 3.141592653589
|
||||||
@@ -12,23 +12,29 @@
|
|||||||
// Fortunately, the Zig Standard Library provides a simple API for interacting
|
// Fortunately, the Zig Standard Library provides a simple API for interacting
|
||||||
// with the file system, see the detail documentation here:
|
// with the file system, see the detail documentation here:
|
||||||
//
|
//
|
||||||
// https://ziglang.org/documentation/master/std/#std.fs
|
// https://ziglang.org/documentation/master/std/#std.Io
|
||||||
//
|
//
|
||||||
// In this exercise, we'll try to:
|
// In this exercise, we'll try to:
|
||||||
// - create a new directory,
|
// - create a new directory,
|
||||||
// - open a file in the directory,
|
// - open a file in the directory,
|
||||||
// - write to the file.
|
// - write to the file.
|
||||||
//
|
//
|
||||||
// import std as always
|
// Note: For simplicity, we write byte-by-byte without buffering.
|
||||||
|
// In real applications, you'd typically use a buffer for better
|
||||||
|
// performance. We'll learn about buffered I/O in a later exercise.
|
||||||
|
//
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main(init: std.process.Init) !void {
|
||||||
|
// default I/O implementation
|
||||||
|
const io = init.io;
|
||||||
|
|
||||||
// first we get the current working directory
|
// first we get the current working directory
|
||||||
const cwd: std.fs.Dir = std.fs.cwd();
|
const cwd: std.Io.Dir = std.Io.Dir.cwd();
|
||||||
|
|
||||||
// then we'll try to make a new directory /output/
|
// then we'll try to make a new directory /output/
|
||||||
// to store our output files.
|
// to store our output files.
|
||||||
cwd.makeDir("output") catch |e| switch (e) {
|
cwd.createDir(io, "output", .default_dir) catch |e| switch (e) {
|
||||||
// there is a chance you might want to run this
|
// there is a chance you might want to run this
|
||||||
// program more than once and the path might already
|
// program more than once and the path might already
|
||||||
// have been created, so we'll have to handle this error
|
// have been created, so we'll have to handle this error
|
||||||
@@ -44,34 +50,38 @@ pub fn main() !void {
|
|||||||
// wait a minute...
|
// wait a minute...
|
||||||
// opening a directory might fail!
|
// opening a directory might fail!
|
||||||
// what should we do here?
|
// what should we do here?
|
||||||
var output_dir: std.fs.Dir = cwd.openDir("output", .{});
|
var output_dir: std.Io.Dir = try cwd.openDir(io, "output", .{});
|
||||||
defer output_dir.close();
|
defer output_dir.close(io);
|
||||||
|
|
||||||
// we try to open the file `zigling.txt`,
|
// we try to open the file `zigling.txt`,
|
||||||
// and propagate any error up
|
// and propagate any error up
|
||||||
const file: std.fs.File = try output_dir.createFile("zigling.txt", .{});
|
const file: std.Io.File = try output_dir.createFile(io, "zigling.txt", .{});
|
||||||
// it is a good habit to close a file after you are done with it
|
// it is a good habit to close a file after you are done with it
|
||||||
// so that other programs can read it and prevent data corruption
|
// so that other programs can read it and prevent data corruption
|
||||||
// but here we are not yet done writing to the file
|
// but here we are not yet done writing to the file
|
||||||
// if only there were a keyword in Zig that
|
// if only there were a keyword in Zig that
|
||||||
// allowed you to "defer" code execution to the end of the scope...
|
// allowed you to "defer" code execution to the end of the scope...
|
||||||
file.close();
|
file.close(io);
|
||||||
|
|
||||||
// you are not allowed to move these two lines above the file closing line!
|
// you are not allowed to move these lines above the file closing line!
|
||||||
const byte_written = try file.write("It's zigling time!");
|
var file_writer = file.writer(io, &.{});
|
||||||
|
const writer = &file_writer.interface;
|
||||||
|
|
||||||
|
const byte_written = try writer.write("It's zigling time!");
|
||||||
std.debug.print("Successfully wrote {d} bytes.\n", .{byte_written});
|
std.debug.print("Successfully wrote {d} bytes.\n", .{byte_written});
|
||||||
}
|
}
|
||||||
// to check if you actually write to the file, you can either,
|
// to check if you actually write to the file, you can either,
|
||||||
// 1. open the file in your text editor, or
|
// 1. open the file in your text editor, or
|
||||||
// 2. print the content of the file in the console with the following command
|
// 2. print the content of the file in the console with one of these commands
|
||||||
// >> cat ./output/zigling.txt
|
// Linux/macOS: >> cat ./output/zigling.txt
|
||||||
|
// Windows (CMD): >> type .\output\zigling.txt
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
// More on Creating files
|
// More on Creating files
|
||||||
//
|
//
|
||||||
// notice in:
|
// notice in:
|
||||||
// ... try output_dir.createFile("zigling.txt", .{});
|
// ... try output_dir.createFile(io, "zigling.txt", .{});
|
||||||
// ^^^
|
// ^^^
|
||||||
// we passed this anonymous struct to the function call
|
// we passed this anonymous struct to the function call
|
||||||
//
|
//
|
||||||
// this is the struct `CreateFlag` with default fields
|
// this is the struct `CreateFlag` with default fields
|
||||||
@@ -86,7 +96,7 @@ pub fn main() !void {
|
|||||||
//
|
//
|
||||||
// Question:
|
// Question:
|
||||||
// - what should you do if you want to also read the file after opening it?
|
// - what should you do if you want to also read the file after opening it?
|
||||||
// - go to the documentation of the struct `std.fs.Dir` here:
|
// - go to the documentation of the struct `std.Io.Dir` here:
|
||||||
// https://ziglang.org/documentation/master/std/#std.fs.Dir
|
// https://ziglang.org/documentation/master/std/#std.Io.Dir
|
||||||
// - can you find a function for opening a file? how about deleting a file?
|
// - can you find a function for opening a file? how about deleting a file?
|
||||||
// - what kind of options can you use with those functions?
|
// - what kind of options can you use with those functions?
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
// Prerequisite :
|
// Prerequisite :
|
||||||
// - exercise/106_files.zig, or
|
// - exercise/106_files.zig, or
|
||||||
// - create a file {project_root}/output/zigling.txt
|
// - create a file {project_root}/output/zigling.txt
|
||||||
// with content `It's zigling time!`(18 byte total)
|
// with content `It's zigling time!`(18 bytes total)
|
||||||
//
|
//
|
||||||
// Now there's no point in writing to a file if we don't read from it, am I right?
|
// Now there's no point in writing to a file if we don't read from it, am I right?
|
||||||
// Let's write a program to read the content of the file that we just created.
|
// Let's write a program to read the content of the file that we just created.
|
||||||
@@ -15,20 +15,26 @@
|
|||||||
// - Then, we initialize an array of characters with all letter 'A', and print it
|
// - Then, we initialize an array of characters with all letter 'A', and print it
|
||||||
// - After that, we read the content of the file into the array
|
// - After that, we read the content of the file into the array
|
||||||
// - Finally, we print out the content we just read
|
// - Finally, we print out the content we just read
|
||||||
|
//
|
||||||
|
// Note: For simplicity, we read byte-by-byte without buffering.
|
||||||
|
// In real applications, you'd typically use a buffer for better
|
||||||
|
// performance. We'll learn about buffered I/O in a later exercise.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main(init: std.process.Init) !void {
|
||||||
|
const io = init.io;
|
||||||
|
|
||||||
// Get the current working directory
|
// Get the current working directory
|
||||||
const cwd = std.fs.cwd();
|
const cwd = std.Io.Dir.cwd();
|
||||||
|
|
||||||
// try to open ./output assuming you did your 106_files exercise
|
// try to open ./output assuming you did your 106_files exercise
|
||||||
var output_dir = try cwd.openDir("output", .{});
|
var output_dir = try cwd.openDir(io, "output", .{});
|
||||||
defer output_dir.close();
|
defer output_dir.close(io);
|
||||||
|
|
||||||
// try to open the file
|
// try to open the file
|
||||||
const file = try output_dir.openFile("zigling.txt", .{});
|
const file = try output_dir.openFile(io, "zigling.txt", .{});
|
||||||
defer file.close();
|
defer file.close(io);
|
||||||
|
|
||||||
// initialize an array of u8 with all letter 'A'
|
// initialize an array of u8 with all letter 'A'
|
||||||
// we need to pick the size of the array, 64 seems like a good number
|
// we need to pick the size of the array, 64 seems like a good number
|
||||||
@@ -37,10 +43,13 @@ pub fn main() !void {
|
|||||||
// this should print out : `AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`
|
// this should print out : `AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`
|
||||||
std.debug.print("{s}\n", .{content});
|
std.debug.print("{s}\n", .{content});
|
||||||
|
|
||||||
|
var file_reader = file.reader(io, &.{});
|
||||||
|
const reader = &file_reader.interface;
|
||||||
|
|
||||||
// okay, seems like a threat of violence is not the answer in this case
|
// okay, seems like a threat of violence is not the answer in this case
|
||||||
// can you go here to find a way to read the content?
|
// can you go here to find a way to read the content?
|
||||||
// https://ziglang.org/documentation/master/std/#std.fs.File
|
// https://ziglang.org/documentation/master/std/#std.Io.Reader
|
||||||
// hint: you might find two answers that are both valid in this case
|
// hint: look for a method that reads into a slice
|
||||||
const bytes_read = zig_read_the_file_or_i_will_fight_you(&content);
|
const bytes_read = zig_read_the_file_or_i_will_fight_you(&content);
|
||||||
|
|
||||||
// Woah, too screamy. I know you're excited for zigling time but tone it down a bit.
|
// Woah, too screamy. I know you're excited for zigling time but tone it down a bit.
|
||||||
175
exercises/114_packed.zig
Normal file
175
exercises/114_packed.zig
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
//
|
||||||
|
// We've already learned plenty about bit manipulation using bitwise operations
|
||||||
|
// in exercices 097 and 098 and in quiz 110. The techniques we already know work
|
||||||
|
// just fine, but creating masks and shifting individual bits around can become
|
||||||
|
// quite tedious and unwieldy pretty quickly.
|
||||||
|
// What if there was a better, a more convenient way to control invidivual bits?
|
||||||
|
//
|
||||||
|
// Luckily, Zig has a keyword for exactly this purpose:
|
||||||
|
//
|
||||||
|
// packed
|
||||||
|
//
|
||||||
|
// It doesn't do anything on its own, to unlock its potential (and to get our
|
||||||
|
// program to compile) we have to attach it either to a struct or to a union
|
||||||
|
// declaration:
|
||||||
|
//
|
||||||
|
// const Foo = packed struct { ... };
|
||||||
|
// const Bar = packed union { ... };
|
||||||
|
//
|
||||||
|
// Now, what does this keyword even do?
|
||||||
|
// To answer this question we first have to talk about *container layouts*.
|
||||||
|
//
|
||||||
|
// Plain structs and unions use the `auto` layout; it gives no guarantees about
|
||||||
|
// their size or the order of the fields they contain, both are fully up to the
|
||||||
|
// compiler (though both size and field order *are* guaranteed to be the same
|
||||||
|
// across any single compilation unit).
|
||||||
|
//
|
||||||
|
// Attaching the `packed` keyword to a container makes it use `packed` layout:
|
||||||
|
// Suddenly, all of its fields are *packed* together tightly without any padding
|
||||||
|
// in between and their order is guaranteed to be the same as the one specified
|
||||||
|
// in our source code. For structs, the size of the container is guaranteed to
|
||||||
|
// be the sum of the (bit-)sizes of all of its fields. For unions, all fields
|
||||||
|
// have to have the exact same (bit-)size (no padding allowed!); the union itself
|
||||||
|
// is also guaranteed to be exactly of this size.
|
||||||
|
//
|
||||||
|
// If you're familiar with C, you might have already heard of structure packing
|
||||||
|
// in a different context: arranging fields in a way that minimizes the amount
|
||||||
|
// of alignment padding between them (or having the compiler do it for you).
|
||||||
|
// This is *not* what Zig's `packed` keyword is for!
|
||||||
|
//
|
||||||
|
// Try to make the comptime assertions below pass:
|
||||||
|
|
||||||
|
const PackedStruct = packed struct {
|
||||||
|
a: u2,
|
||||||
|
b: u?,
|
||||||
|
};
|
||||||
|
|
||||||
|
comptime {
|
||||||
|
assert(@bitSizeOf(PackedStruct) == 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
const PackedUnion = packed union {
|
||||||
|
a: bool,
|
||||||
|
b: u?,
|
||||||
|
};
|
||||||
|
|
||||||
|
comptime {
|
||||||
|
assert(@bitSizeOf(PackedUnion) == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, how can we use this new knowledge to manipulate some bits?
|
||||||
|
//
|
||||||
|
// As you might have already guessed, `packed` containers are very useful for
|
||||||
|
// representing bitflags or other tightly packed collections of bit-sized values
|
||||||
|
// often found in file headers and network protocols.
|
||||||
|
//
|
||||||
|
// Let's take a look at a real-life example:
|
||||||
|
// The LZ4 compression format (†) specifies a frame format to describe compressed
|
||||||
|
// data. Each LZ4 frame has a descriptor, and each descriptor contains a 'FLG'
|
||||||
|
// byte that specifies the contents of its frame:
|
||||||
|
|
||||||
|
/// | BitNb | 7-6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||||||
|
/// | ------- |-------|-------|----------|------|----------|--------|------|
|
||||||
|
/// |FieldName|Version|B.Indep|B.Checksum|C.Size|C.Checksum|Reserved|DictID|
|
||||||
|
///
|
||||||
|
const FLG = packed struct(u8) {
|
||||||
|
dict_id: bool,
|
||||||
|
reserved: u1 = 0,
|
||||||
|
content_checksum: bool,
|
||||||
|
content_size: bool,
|
||||||
|
block_checksum: bool,
|
||||||
|
block_indepencence: bool,
|
||||||
|
version: u2,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wait, what's with the `(u8)` after the `struct` keyword? What do integers have
|
||||||
|
// to do with all of this?
|
||||||
|
// Well, this is a good opportunity to come clear about something:
|
||||||
|
// packed structs and packed unions aren't actually structs or unions at all...
|
||||||
|
// They are merely integers in disguise! For all intents and purposes, their
|
||||||
|
// fields are just convenient names for ranges of their underlying bits. To make
|
||||||
|
// it easier to enforce size requirements for packed containers, Zig allows us
|
||||||
|
// to specify a *backing integer* for them, just like for enums.
|
||||||
|
//
|
||||||
|
// In the case of `FLG`, we want our struct to occupy exactly a single byte, so
|
||||||
|
// we specify `u8` as the backing integer. It's safe to convert between a packed
|
||||||
|
// container and its backing integer using the builtin `@bitCast`.
|
||||||
|
// The LZ4 spec also mandates that reserved bits must always be zero, so it's
|
||||||
|
// good practice to set `0` as a default value for `reserved`.
|
||||||
|
//
|
||||||
|
// The fields of a packed struct start at the least significant bit of its backing
|
||||||
|
// integer and end at its most significant bit. This is the case no matter what
|
||||||
|
// endianness our target has.
|
||||||
|
//
|
||||||
|
// Try to silence the complaints below:
|
||||||
|
|
||||||
|
const Bits = packed struct(u4) {
|
||||||
|
a: u1 = 0,
|
||||||
|
b: u1 = 0,
|
||||||
|
c: u1 = 0,
|
||||||
|
d: u1 = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn main() void {
|
||||||
|
{
|
||||||
|
const expected: Bits = @bitCast(@as(u4, 0b1000));
|
||||||
|
const my_bits: Bits = .{};
|
||||||
|
if (my_bits != expected) complain(my_bits, expected, @src());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const expected: Bits = @bitCast(@as(u4, 0b0001));
|
||||||
|
const my_bits: Bits = .{};
|
||||||
|
if (my_bits != expected) complain(my_bits, expected, @src());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const expected: Bits = @bitCast(@as(u4, 0b0010));
|
||||||
|
const my_bits: Bits = .{};
|
||||||
|
if (my_bits != expected) complain(my_bits, expected, @src());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const expected: Bits = @bitCast(@as(u4, 0b0011));
|
||||||
|
const my_bits: Bits = .{};
|
||||||
|
if (my_bits != expected) complain(my_bits, expected, @src());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const expected: Bits = @bitCast(@as(u4, 0b1101));
|
||||||
|
const my_bits: Bits = .{};
|
||||||
|
if (my_bits != expected) complain(my_bits, expected, @src());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// As we can see, equality comparisons (`==` and `!=`) work for packed structs.
|
||||||
|
// They also work for packed unions. However, since packed containers are not
|
||||||
|
// naturally ordered, we can't use any other comparison operators on them.
|
||||||
|
//
|
||||||
|
// It's also possible to use packed containers in `switch` statements, which we
|
||||||
|
// will cover in the next exercise!
|
||||||
|
//
|
||||||
|
// Since packed containers make very strong guarantees about their memory layout,
|
||||||
|
// only a handful of types are eligible to be part of them.
|
||||||
|
// The following types are allowed as field types:
|
||||||
|
//
|
||||||
|
// - integers
|
||||||
|
// - floats
|
||||||
|
// - bool
|
||||||
|
// - void
|
||||||
|
// - enums with explicit backing integers
|
||||||
|
// - packed unions
|
||||||
|
// - packed structs
|
||||||
|
//
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
|
fn complain(my_bits: Bits, expected: Bits, src_loc: std.builtin.SourceLocation) void {
|
||||||
|
std.debug.print(
|
||||||
|
"That's not quite right! You've got 0b{b:0>4}, but we want 0b{b:0>4} in line {d}.\n",
|
||||||
|
.{ @as(u4, @bitCast(my_bits)), @as(u4, @bitCast(expected)), src_loc.line },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// (†) https://github.com/lz4/lz4/blob/5c4c1fb2354133e1f3b087a341576985f8114bd5/doc/lz4_Frame_format.md#frame-descriptor
|
||||||
78
exercises/115_packed2.zig
Normal file
78
exercises/115_packed2.zig
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
//
|
||||||
|
// We've already learned about switch statements in exercises 030, 031 and 108.
|
||||||
|
// They also work with packed containers:
|
||||||
|
|
||||||
|
const S = packed struct(u2) {
|
||||||
|
a: bool,
|
||||||
|
b: i1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try to make it compile without adding an `else` prong!
|
||||||
|
|
||||||
|
comptime {
|
||||||
|
const s: S = .{ .a = true, .b = -1 };
|
||||||
|
switch (s) {
|
||||||
|
.{ .a = true, .b = -1 } => {}, // ok!
|
||||||
|
.{ .a = true, .b = ??? },
|
||||||
|
.{ .a = ???, .b = 0 },
|
||||||
|
.{ .a = ???, .b = ??? },
|
||||||
|
=> @compileError("We don't want to end up here!"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// As we can see, switching on packed structs is pretty straightforward.
|
||||||
|
// When switching on packed unions however, we'll realize that a packed
|
||||||
|
// union never keeps track of its active tag, not even in debug mode! This
|
||||||
|
// means that packed unions compare solely by their bit pattern (again, just
|
||||||
|
// like integers).
|
||||||
|
|
||||||
|
const U = packed union(u2) {
|
||||||
|
a: u2,
|
||||||
|
b: i2,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find and remove the duplicate case!
|
||||||
|
|
||||||
|
comptime {
|
||||||
|
const u: U = .{ .a = 3 };
|
||||||
|
switch (u) {
|
||||||
|
.{ .a = 3 } => {}, // ok!
|
||||||
|
.{ .a = 2 },
|
||||||
|
.{ .b = 1 },
|
||||||
|
.{ .b = -1 },
|
||||||
|
.{ .a = 0 },
|
||||||
|
=> @compileError("We don't want to end up here!"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since packed unions don't have the concept of an active tag, it's always legal
|
||||||
|
// to access any of their fields. This can be useful to view the same data from
|
||||||
|
// different perspectives seamlessly.
|
||||||
|
//
|
||||||
|
// Try to make the float below negative:
|
||||||
|
|
||||||
|
/// IEEE 754 half precision float
|
||||||
|
const Float = packed union(u16) {
|
||||||
|
value: f16,
|
||||||
|
bits: packed struct(u16) {
|
||||||
|
mantissa: u10,
|
||||||
|
exponent: u5,
|
||||||
|
sign: u1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn main() void {
|
||||||
|
// Reminder: if the sign bit of a float is set, the number is negative!
|
||||||
|
|
||||||
|
var number: Float = .{ .value = 2.34 };
|
||||||
|
number.bits.??? = ???;
|
||||||
|
if (number.value != -2.34) {
|
||||||
|
std.debug.print("Make it negative!\n", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This concludes our introduction to packed containers. The next time you need
|
||||||
|
// control over individual bits, keep them in mind as a potent alternative!
|
||||||
|
//
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
BIN
images/ziglings_dark.jpg
Normal file
BIN
images/ziglings_dark.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 97 KiB |
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/sh
|
||||||
#
|
#
|
||||||
# "I will be a shieldmaiden no longer,
|
# "I will be a shieldmaiden no longer,
|
||||||
# nor vie with the great Riders, nor
|
# nor vie with the great Riders, nor
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/sh
|
||||||
#
|
#
|
||||||
# "How do you pick up the threads of an old life?
|
# "How do you pick up the threads of an old life?
|
||||||
# How do you go on, when in your heart you begin
|
# How do you go on, when in your heart you begin
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
--- exercises/001_hello.zig 2023-10-03 22:15:22.122241138 +0200
|
--- exercises/001_hello.zig 2026-01-04 14:04:52.752848018 +0100
|
||||||
+++ answers/001_hello.zig 2023-10-05 20:04:06.846096282 +0200
|
+++ answers/001_hello.zig 2026-01-04 14:04:54.209877278 +0100
|
||||||
@@ -16,6 +16,6 @@
|
@@ -16,6 +16,6 @@
|
||||||
//
|
//
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
--- exercises/009_if.zig 2023-10-03 22:15:22.122241138 +0200
|
--- exercises/009_if.zig 2025-11-28 14:40:19.301738185 +0100
|
||||||
+++ answers/009_if.zig 2023-10-05 20:04:06.882763636 +0200
|
+++ answers/009_if.zig 2025-11-28 14:39:07.756077340 +0100
|
||||||
@@ -24,7 +24,7 @@
|
@@ -24,7 +24,7 @@
|
||||||
const foo = 1;
|
const foo = 42;
|
||||||
|
|
||||||
// Please fix this condition:
|
// Please fix this condition:
|
||||||
- if (foo) {
|
- if (foo) {
|
||||||
+ if (foo == 1) {
|
+ if (foo == 42) {
|
||||||
// We want our program to print this message!
|
// We want our program to print this message!
|
||||||
std.debug.print("Foo is 1!\n", .{});
|
std.debug.print("Foo is 42!\n", .{});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
--- exercises/026_hello2.zig 2025-07-22 09:55:51.337832401 +0200
|
--- exercises/026_hello2.zig 2026-01-09 22:51:45.803358789 +0100
|
||||||
+++ answers/026_hello2.zig 2025-07-22 10:00:11.233348058 +0200
|
+++ answers/026_hello2.zig 2026-01-09 22:50:46.016166527 +0100
|
||||||
@@ -23,5 +23,5 @@
|
@@ -28,5 +28,5 @@
|
||||||
// to be able to pass it up as a return value of main().
|
// to be able to pass it up as a return value of main().
|
||||||
//
|
//
|
||||||
// We just learned of a single statement which can accomplish this.
|
// We just learned of a single statement which can accomplish this.
|
||||||
- stdout.interface.print("Hello world!\n", .{});
|
- stdout.print("Hello world!\n", .{});
|
||||||
+ try stdout.interface.print("Hello world!\n", .{});
|
+ try stdout.print("Hello world!\n", .{});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
--- exercises/034_quiz4.zig 2025-07-22 09:55:51.337832401 +0200
|
--- exercises/034_quiz4.zig 2026-01-09 22:45:53.115325559 +0100
|
||||||
+++ answers/034_quiz4.zig 2025-07-22 10:05:08.320323184 +0200
|
+++ answers/034_quiz4.zig 2026-01-09 22:45:15.658578603 +0100
|
||||||
@@ -9,10 +9,10 @@
|
@@ -14,7 +14,7 @@
|
||||||
|
var stdout_writer = std.Io.File.stdout().writer(io, &.{});
|
||||||
const NumError = error{IllegalNumber};
|
const stdout = &stdout_writer.interface;
|
||||||
|
|
||||||
-pub fn main() void {
|
|
||||||
+pub fn main() !void {
|
|
||||||
var stdout = std.fs.File.stdout().writer(&.{});
|
|
||||||
|
|
||||||
- const my_num: u32 = getNumber();
|
- const my_num: u32 = getNumber();
|
||||||
+ const my_num: u32 = try getNumber();
|
+ const my_num: u32 = try getNumber();
|
||||||
|
|
||||||
try stdout.interface.print("my_num={}\n", .{my_num});
|
try stdout.print("my_num={}\n", .{my_num});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
--- exercises/060_floats.zig 2025-03-03 20:23:40.255443963 +0400
|
--- exercises/060_floats.zig 2026-04-03 14:40:17.582344768 +0200
|
||||||
+++ answers/060_floats.zig 2025-03-03 20:29:58.554854977 +0400
|
+++ answers/060_floats.zig 2026-04-03 14:49:26.997886326 +0200
|
||||||
@@ -43,7 +43,7 @@
|
@@ -43,7 +43,7 @@
|
||||||
//
|
//
|
||||||
// We'll convert this weight from pounds to metric units at a
|
// We'll convert this weight from pounds to metric units at a
|
||||||
// conversion of 0.453592 kg to the pound.
|
// conversion of 0.453592 kg to the pound.
|
||||||
- const shuttle_weight: f16 = 0.453592 * 4480e3;
|
- const shuttle_weight: f16 = 0.453592 * 4480e3;
|
||||||
+ const shuttle_weight: f32 = 0.453592 * 4.480e3;
|
+ const shuttle_weight: f32 = 0.453592 * 4480e3;
|
||||||
|
|
||||||
// By default, float values are formatted in scientific
|
// By default, float values are formatted in standard decimal
|
||||||
// notation. Try experimenting with '{d}' and '{d:.3}' to see
|
// notation. Experiment with '{d}' and '{d:.3}' to see how
|
||||||
|
@@ -51,7 +51,7 @@
|
||||||
|
// scientific notation.
|
||||||
|
// NOTE: The weight of the shuttle is a huge number, a scientific notation
|
||||||
|
// may be more appropriate.
|
||||||
|
- print("Shuttle liftoff weight: {d:.0} metric tons\n", .{shuttle_weight / 1e3});
|
||||||
|
+ print("Shuttle liftoff weight: {e:.3} metric tons\n", .{shuttle_weight / 1e3});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Floating further:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
--- exercises/065_builtins2.zig 2025-06-17 13:58:07.857258167 +0200
|
--- exercises/065_builtins2.zig 2026-02-27 13:10:36
|
||||||
+++ answers/065_builtins2.zig 2025-06-17 13:56:36.630415938 +0200
|
+++ answers/065_builtins2.zig 2026-02-27 13:10:52
|
||||||
@@ -58,7 +58,7 @@
|
@@ -58,7 +58,7 @@
|
||||||
// Oops! We cannot leave the 'me' and 'myself' fields
|
// Oops! We cannot leave the 'me' and 'myself' fields
|
||||||
// undefined. Please set them here:
|
// undefined. Please set them here:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
--- exercises/067_comptime2.zig 2023-11-21 14:36:12.080295365 +0100
|
--- exercises/067_comptime2.zig 2025-12-07 22:51:24.396031248 +0100
|
||||||
+++ answers/067_comptime2.zig 2023-11-21 15:11:50.814098876 +0100
|
+++ answers/067_comptime2.zig 2025-12-07 22:50:18.721616293 +0100
|
||||||
@@ -35,7 +35,7 @@
|
@@ -36,7 +36,7 @@
|
||||||
// In this contrived example, we've decided to allocate some
|
// In this contrived example, we've decided to allocate some
|
||||||
// arrays using a variable count! But something's missing...
|
// arrays using a variable count! But something's missing...
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,11 +1,35 @@
|
|||||||
--- exercises/074_comptime9.zig 2023-10-03 22:15:22.125574535 +0200
|
--- exercises/074_comptime9.zig 2026-04-11 21:35:08.132459373 -0700
|
||||||
+++ answers/074_comptime9.zig 2023-10-05 20:04:07.176102462 +0200
|
+++ answers/074_comptime9.zig 2026-04-12 07:13:37.971010827 -0700
|
||||||
@@ -39,7 +39,7 @@
|
@@ -27,12 +27,12 @@
|
||||||
|
start, // Ready to start a new animal.
|
||||||
|
l, // This means we've seen an "l", so if we see an "m", we know it's a Llama.
|
||||||
|
};
|
||||||
|
- var state = State.start;
|
||||||
|
+ comptime var state = State.start;
|
||||||
|
|
||||||
// And here's the function. Note that the return value type
|
// We return an array of animals representing the creature. (This is why we
|
||||||
// depends on one of the input arguments!
|
// really needed the 'count' parameter. Arrays need a size.)
|
||||||
-fn makeLlamas(count: usize) [count]u8 {
|
var animals: [count]Animal = .{undefined} ** count;
|
||||||
+fn makeLlamas(comptime count: usize) [count]u8 {
|
- var next_animal: usize = 0;
|
||||||
var temp: [count]u8 = undefined;
|
+ comptime var next_animal: usize = 0;
|
||||||
var i = 0;
|
|
||||||
|
|
||||||
|
inline for (fmt) |char| {
|
||||||
|
|
||||||
|
@@ -56,7 +56,7 @@
|
||||||
|
//
|
||||||
|
// What do you think happens with Gators? Do they join with
|
||||||
|
// other animals or is this an error?
|
||||||
|
- 'g' => ???,
|
||||||
|
+ 'g' => @compileError("Gators refuse to join with other animals."),
|
||||||
|
|
||||||
|
else => @compileError("No animal starts with '" ++ char ++ "'!"),
|
||||||
|
},
|
||||||
|
@@ -68,7 +68,7 @@
|
||||||
|
next_animal += 1;
|
||||||
|
// Something is missing here. After we finish a Llama, we
|
||||||
|
// need to be ready to _start_ over with a new animal...
|
||||||
|
- ???
|
||||||
|
+ state = .start;
|
||||||
|
},
|
||||||
|
|
||||||
|
else => @compileError("Only llamas start with 'l'!"),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
--- exercises/082_anonymous_structs3.zig 2025-03-14 16:41:17.892873287 +0200
|
--- exercises/082_anonymous_structs3.zig 2026-02-27 13:05:46
|
||||||
+++ answers/082_anonymous_structs3.zig 2025-03-14 16:40:56.043829543 +0200
|
+++ answers/082_anonymous_structs3.zig 2026-02-27 13:07:22
|
||||||
@@ -82,14 +82,14 @@
|
@@ -82,14 +82,14 @@
|
||||||
// @typeInfo(Circle).@"struct".fields
|
// @typeInfo(Circle).@"struct".fields
|
||||||
//
|
//
|
||||||
@@ -17,9 +17,9 @@
|
|||||||
// 3. Print the field's name, type, and value.
|
// 3. Print the field's name, type, and value.
|
||||||
//
|
//
|
||||||
// Each 'field' in this loop is one of these:
|
// Each 'field' in this loop is one of these:
|
||||||
@@ -119,9 +119,9 @@
|
@@ -123,9 +123,9 @@
|
||||||
|
// for declarations. If it's a value, it looks for data.
|
||||||
//
|
//
|
||||||
// The first field should print as: "0"(bool):true
|
|
||||||
print("\"{s}\"({any}):{any} ", .{
|
print("\"{s}\"({any}):{any} ", .{
|
||||||
- field.???,
|
- field.???,
|
||||||
- field.???,
|
- field.???,
|
||||||
|
|||||||
11
patches/patches/084_async.patch
Normal file
11
patches/patches/084_async.patch
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
--- exercises/084_async.zig 2026-04-01 20:40:08.904999609 +0200
|
||||||
|
+++ answers/084_async.zig 2026-04-01 20:40:05.641933231 +0200
|
||||||
|
@@ -37,7 +37,7 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn main(init: std.process.Init) !void {
|
||||||
|
- const io = init.???;
|
||||||
|
+ const io = init.io;
|
||||||
|
|
||||||
|
// Get the current wall-clock time using the Io interface.
|
||||||
|
// Hint: Timestamp.now() takes an Io and a Clock type (.real = wall clock).
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
--- exercises/092_interfaces.zig 2023-10-03 22:15:22.125574535 +0200
|
--- exercises/084_interfaces.zig 2026-04-03 19:24:51.764327692 +0200
|
||||||
+++ answers/092_interfaces.zig 2023-10-05 20:04:07.259437354 +0200
|
+++ answers/084_interfaces.zig 2026-04-03 19:27:31.552579474 +0200
|
||||||
@@ -106,7 +106,7 @@
|
@@ -106,7 +106,7 @@
|
||||||
for (my_insects) |insect| {
|
for (my_insects) |insect| {
|
||||||
// Almost done! We want to print() each insect with a
|
// Almost done! We want to print() each insect with a
|
||||||
11
patches/patches/085_async.patch
Normal file
11
patches/patches/085_async.patch
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
--- exercises/085_async.zig 2026-04-04 16:01:01.509555724 +0200
|
||||||
|
+++ answers/085_async.zig 2026-04-04 16:00:58.541495688 +0200
|
||||||
|
@@ -38,7 +38,7 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn main(init: std.process.Init) !void {
|
||||||
|
- const io = init.???;
|
||||||
|
+ const io = init.io;
|
||||||
|
|
||||||
|
// Get the current wall-clock time using the Io interface.
|
||||||
|
// Hint: Timestamp.now() takes an Io and a Clock type (.real = wall clock).
|
||||||
11
patches/patches/086_async2.patch
Normal file
11
patches/patches/086_async2.patch
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
--- exercises/086_async2.zig 2026-04-05 12:41:11.350626443 +0200
|
||||||
|
+++ answers/086_async2.zig 2026-04-05 12:42:00.879791167 +0200
|
||||||
|
@@ -44,7 +44,7 @@
|
||||||
|
|
||||||
|
// Now collect the result. What method on Future gives us
|
||||||
|
// the value, blocking until it's ready?
|
||||||
|
- const answer = future.???(io);
|
||||||
|
+ const answer = future.await(io);
|
||||||
|
|
||||||
|
print("The answer is: {}\n", .{answer});
|
||||||
|
}
|
||||||
11
patches/patches/087_async3.patch
Normal file
11
patches/patches/087_async3.patch
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
--- exercises/087_async3.zig 2026-04-05 16:12:48.317265515 +0200
|
||||||
|
+++ answers/087_async3.zig 2026-04-05 16:12:52.269343030 +0200
|
||||||
|
@@ -28,7 +28,7 @@
|
||||||
|
// Launch both tasks asynchronously.
|
||||||
|
var future_a = io.async(slowAdd, .{ 1, 2 });
|
||||||
|
defer _ = future_a.cancel(io);
|
||||||
|
- var future_b = ???(slowMul, .{ 6, 7 });
|
||||||
|
+ var future_b = io.async(slowMul, .{ 6, 7 });
|
||||||
|
defer _ = future_b.cancel(io);
|
||||||
|
|
||||||
|
// Await both results.
|
||||||
11
patches/patches/088_async4.patch
Normal file
11
patches/patches/088_async4.patch
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
--- exercises/088_async4.zig 2026-04-06 12:22:06.643385622 +0200
|
||||||
|
+++ answers/088_async4.zig 2026-04-06 12:22:11.820491035 +0200
|
||||||
|
@@ -38,7 +38,7 @@
|
||||||
|
|
||||||
|
// Wait for all tasks to finish.
|
||||||
|
// What Group method blocks until all tasks complete?
|
||||||
|
- try group.???(io);
|
||||||
|
+ try group.await(io);
|
||||||
|
|
||||||
|
print("All tasks finished!\n", .{});
|
||||||
|
}
|
||||||
11
patches/patches/089_async5.patch
Normal file
11
patches/patches/089_async5.patch
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
--- exercises/089_async5.zig 2026-04-06 14:38:54.443726849 +0200
|
||||||
|
+++ answers/089_async5.zig 2026-04-06 14:38:39.945438309 +0200
|
||||||
|
@@ -46,7 +46,7 @@
|
||||||
|
|
||||||
|
// We don't want to wait 10 seconds!
|
||||||
|
// Which Future method requests cancellation AND returns the result?
|
||||||
|
- const result = future.???(io);
|
||||||
|
+ const result = future.cancel(io);
|
||||||
|
|
||||||
|
print("Task returned: {}\n", .{result});
|
||||||
|
}
|
||||||
11
patches/patches/090_async6.patch
Normal file
11
patches/patches/090_async6.patch
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
--- exercises/090_async6.zig 2026-04-06 18:49:37.232023422 +0200
|
||||||
|
+++ answers/090_async6.zig 2026-04-06 18:49:22.189720687 +0200
|
||||||
|
@@ -52,7 +52,7 @@
|
||||||
|
|
||||||
|
// Wait for the first finisher.
|
||||||
|
// What Select method returns the first completed result?
|
||||||
|
- const winner = try sel.???();
|
||||||
|
+ const winner = try sel.await();
|
||||||
|
|
||||||
|
switch (winner) {
|
||||||
|
.hare => |msg| print("Hare: {s}\n", .{msg}),
|
||||||
13
patches/patches/091_async7.patch
Normal file
13
patches/patches/091_async7.patch
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
--- exercises/091_async7.zig 2026-04-02 10:50:08.142508099 +0200
|
||||||
|
+++ answers/091_async7.zig 2026-04-02 10:49:59.629341593 +0200
|
||||||
|
@@ -49,8 +49,8 @@
|
||||||
|
for (0..times) |_| {
|
||||||
|
// Acquire the lock before modifying shared state.
|
||||||
|
// What Mutex method blocks until the lock is acquired?
|
||||||
|
- state.mutex.??? catch return;
|
||||||
|
- defer state.mutex.unlock(); // <-- what's missing here?
|
||||||
|
+ state.mutex.lock(io) catch return;
|
||||||
|
+ defer state.mutex.unlock(io);
|
||||||
|
|
||||||
|
state.counter += 1;
|
||||||
|
}
|
||||||
11
patches/patches/092_async8.patch
Normal file
11
patches/patches/092_async8.patch
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
--- exercises/092_async8.zig 2026-04-02 10:49:27.925721496 +0200
|
||||||
|
+++ answers/092_async8.zig 2026-04-02 10:49:31.694795212 +0200
|
||||||
|
@@ -43,7 +43,7 @@
|
||||||
|
// Send numbers 1 through 10 into the queue.
|
||||||
|
for (1..11) |i| {
|
||||||
|
// What Queue method sends a single element, blocking if full?
|
||||||
|
- queue.???(io, @intCast(i)) catch return;
|
||||||
|
+ queue.putOne(io, @intCast(i)) catch return;
|
||||||
|
}
|
||||||
|
// Signal that we're done sending.
|
||||||
|
queue.close(io);
|
||||||
15
patches/patches/093_async9.patch
Normal file
15
patches/patches/093_async9.patch
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
--- exercises/093_async9.zig 2026-04-14 22:20:23.519107582 +0200
|
||||||
|
+++ answers/093_async9.zig 2026-04-14 22:06:30.606912044 +0200
|
||||||
|
@@ -63,10 +63,10 @@
|
||||||
|
|
||||||
|
// Launch two workers, each searching half the array.
|
||||||
|
// Remember, we want them to be guaranteed separate units of concurrency.
|
||||||
|
- var f1 = ???(searchThreshold, .{ io, data[0..mid], threshold, 0, 0, &queue });
|
||||||
|
+ var f1 = try io.concurrent(searchThreshold, .{ io, data[0..mid], threshold, 0, 0, &queue });
|
||||||
|
defer _ = f1.cancel(io);
|
||||||
|
|
||||||
|
- var f2 = ???(searchThreshold, .{ io, data[mid..], threshold, mid, 1, &queue });
|
||||||
|
+ var f2 = try io.concurrent(searchThreshold, .{ io, data[mid..], threshold, mid, 1, &queue });
|
||||||
|
defer _ = f2.cancel(io);
|
||||||
|
|
||||||
|
// Wait for the first result.
|
||||||
13
patches/patches/094_async10.patch
Normal file
13
patches/patches/094_async10.patch
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
--- exercises/094_async10.zig 2026-04-06 19:36:59.873966580 +0200
|
||||||
|
+++ answers/094_async10.zig 2026-04-06 19:37:12.416216872 +0200
|
||||||
|
@@ -51,8 +51,8 @@
|
||||||
|
|
||||||
|
// Protect this section from cancellation.
|
||||||
|
// What method swaps the cancel protection state?
|
||||||
|
- const old = io.???(.blocked);
|
||||||
|
- defer _ = io.???(old);
|
||||||
|
+ const old = io.swapCancelProtection(.blocked);
|
||||||
|
+ defer _ = io.swapCancelProtection(old);
|
||||||
|
|
||||||
|
// This sleep will NOT return error.Canceled even though
|
||||||
|
// we get canceled during it — protection is active!
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
--- exercises/095_for3.zig 2023-10-03 22:15:22.125574535 +0200
|
|
||||||
+++ answers/095_for3.zig 2023-10-05 20:04:07.272770937 +0200
|
|
||||||
@@ -54,7 +54,7 @@
|
|
||||||
|
|
||||||
// I want to print every number between 1 and 20 that is NOT
|
|
||||||
// divisible by 3 or 5.
|
|
||||||
- for (???) |n| {
|
|
||||||
+ for (1..21) |n| {
|
|
||||||
|
|
||||||
// The '%' symbol is the "modulo" operator and it
|
|
||||||
// returns the remainder after division.
|
|
||||||
56
patches/patches/095_quiz_async.patch
Normal file
56
patches/patches/095_quiz_async.patch
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
--- exercises/095_quiz_async.zig 2026-04-13 19:11:04.173440326 -0700
|
||||||
|
+++ answers/095_quiz_async.zig 2026-04-13 19:10:31.618592222 -0700
|
||||||
|
@@ -51,7 +51,7 @@
|
||||||
|
fn addReading(self: *GardenWeather, io: std.Io, reading: Reading) void {
|
||||||
|
// Bug 1: The collector needs to lock before modifying
|
||||||
|
// shared state. What Mutex method acquires the lock?
|
||||||
|
- self.mutex.???(io) catch return;
|
||||||
|
+ self.mutex.lock(io) catch return;
|
||||||
|
defer self.mutex.unlock(io);
|
||||||
|
|
||||||
|
switch (reading.sensor_type) {
|
||||||
|
@@ -78,7 +78,7 @@
|
||||||
|
// Bug 2: The collector needs guaranteed concurrency.
|
||||||
|
// What method ensures a separate unit of concurrency?
|
||||||
|
// (Don't forget: it can fail!)
|
||||||
|
- var collector_future = try io.???(collector, .{ io, &queue, &weather });
|
||||||
|
+ var collector_future = try io.concurrent(collector, .{ io, &queue, &weather });
|
||||||
|
defer _ = collector_future.cancel(io);
|
||||||
|
|
||||||
|
// Sensor group: the sensors can use async — they just need
|
||||||
|
@@ -91,22 +91,22 @@
|
||||||
|
|
||||||
|
// Bug 3: Wait for ALL sensors to finish sending their readings.
|
||||||
|
// What Group method blocks until all tasks complete?
|
||||||
|
- try sensors.???(io);
|
||||||
|
+ try sensors.await(io);
|
||||||
|
|
||||||
|
// All sensors done — close the queue so the collector knows
|
||||||
|
// there's no more data coming.
|
||||||
|
queue.close(io);
|
||||||
|
|
||||||
|
// Bug 4: How do we wait for the collector to drain the remaining queue?
|
||||||
|
- _ = collector_future.???(io);
|
||||||
|
+ _ = collector_future.await(io);
|
||||||
|
|
||||||
|
// Now write the garden report. This is critical — it must
|
||||||
|
// NOT be interrupted, even if something tries to cancel us!
|
||||||
|
//
|
||||||
|
// Bug 5: Protect this section from cancellation.
|
||||||
|
// What Io method swaps the cancel protection state?
|
||||||
|
- const old_protection = io.???(.blocked);
|
||||||
|
- defer _ = io.???(old_protection);
|
||||||
|
+ const old_protection = io.swapCancelProtection(.blocked);
|
||||||
|
+ defer _ = io.swapCancelProtection(old_protection);
|
||||||
|
|
||||||
|
printGardenReport(&weather);
|
||||||
|
}
|
||||||
|
@@ -128,7 +128,7 @@
|
||||||
|
|
||||||
|
// Bug 6: Send the reading into the queue.
|
||||||
|
// What Queue method sends a single element?
|
||||||
|
- queue.???(io, reading) catch return;
|
||||||
|
+ queue.putOne(io, reading) catch return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
--- exercises/093_hello_c.zig 2023-10-03 22:15:22.125574535 +0200
|
--- exercises/096_hello_c.zig 2025-08-15 15:17:57.839348063 +0200
|
||||||
+++ answers/093_hello_c.zig 2023-10-05 20:04:07.262770750 +0200
|
+++ answers/096_hello_c.zig 2026-04-03 13:09:26.195163128 +0200
|
||||||
@@ -54,7 +54,7 @@
|
@@ -54,7 +54,7 @@
|
||||||
//
|
//
|
||||||
// In this exercise we use 'write' to output 17 chars,
|
// In this exercise we use 'write' to output 17 chars,
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
--- exercises/097_bit_manipulation.zig 2025-05-12 21:25:03.395385743 +0200
|
|
||||||
+++ answers/097_bit_manipulation.zig 2025-05-12 21:22:57.472986976 +0200
|
|
||||||
@@ -80,7 +80,7 @@
|
|
||||||
y ^= x;
|
|
||||||
|
|
||||||
// What must be written here?
|
|
||||||
- ???;
|
|
||||||
+ x ^= y;
|
|
||||||
|
|
||||||
print("x = {b}; y = {b}\n", .{ x, y });
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
--- exercises/094_c_math.zig 2024-02-28 12:50:35.789939935 +0100
|
--- exercises/097_c_math.zig 2025-08-15 15:17:57.839348063 +0200
|
||||||
+++ answers/094_c_math.zig 2024-02-28 12:53:57.910309471 +0100
|
+++ answers/097_c_math.zig 2026-04-03 13:09:32.059278502 +0200
|
||||||
@@ -26,7 +26,7 @@
|
@@ -26,7 +26,7 @@
|
||||||
|
|
||||||
const c = @cImport({
|
const c = @cImport({
|
||||||
20
patches/patches/098_for3.patch
Normal file
20
patches/patches/098_for3.patch
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
--- exercises/098_for3.zig 2026-03-20 19:23:48.873150100 +0100
|
||||||
|
+++ answers/098_for3.zig 2026-04-03 13:09:39.916433087 +0200
|
||||||
|
@@ -56,7 +56,7 @@
|
||||||
|
|
||||||
|
// I want to print every number between 1 and 20 that is NOT
|
||||||
|
// divisible by 3 or 5.
|
||||||
|
- for (???) |n| {
|
||||||
|
+ for (1..21) |n| {
|
||||||
|
|
||||||
|
// The '%' symbol is the "modulo" operator and it
|
||||||
|
// returns the remainder after division.
|
||||||
|
@@ -68,7 +68,7 @@
|
||||||
|
std.debug.print("\n", .{});
|
||||||
|
|
||||||
|
// Let's also print every number from 1 through 15
|
||||||
|
- for (???) |n| {
|
||||||
|
+ for (1..16) |n| {
|
||||||
|
std.debug.print("{} ", .{n});
|
||||||
|
}
|
||||||
|
std.debug.print("\n", .{});
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
--- exercises/096_memory_allocation.zig 2023-11-21 14:55:33.805678390 +0100
|
--- exercises/099_memory_allocation.zig 2025-08-15 15:17:57.839348063 +0200
|
||||||
+++ answers/096_memory_allocation.zig 2023-11-21 14:56:00.236163484 +0100
|
+++ answers/099_memory_allocation.zig 2026-04-03 13:09:47.403580391 +0200
|
||||||
@@ -64,7 +64,7 @@
|
@@ -64,7 +64,7 @@
|
||||||
const allocator = arena.allocator();
|
const allocator = arena.allocator();
|
||||||
|
|
||||||
11
patches/patches/100_bit_manipulation.patch
Normal file
11
patches/patches/100_bit_manipulation.patch
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
--- exercises/100_bit_manipulation.zig 2025-08-15 15:17:57.839348063 +0200
|
||||||
|
+++ answers/100_bit_manipulation.zig 2026-04-02 10:51:15.795831343 +0200
|
||||||
|
@@ -80,7 +80,7 @@
|
||||||
|
y ^= x;
|
||||||
|
|
||||||
|
// What must be written here?
|
||||||
|
- ???;
|
||||||
|
+ x ^= y;
|
||||||
|
|
||||||
|
print("x = {b}; y = {b}\n", .{ x, y });
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
--- exercises/098_bit_manipulation2.zig 2023-10-03 22:15:22.125574535 +0200
|
--- exercises/101_bit_manipulation2.zig 2025-08-15 15:17:57.839348063 +0200
|
||||||
+++ answers/098_bit_manipulation2.zig 2023-10-05 20:04:07.286104520 +0200
|
+++ answers/101_bit_manipulation2.zig 2026-04-02 10:51:15.797831382 +0200
|
||||||
@@ -60,5 +60,5 @@
|
@@ -60,5 +60,5 @@
|
||||||
// and if so, we know the given string is a pangram
|
// and if so, we know the given string is a pangram
|
||||||
//
|
//
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
--- exercises/099_formatting.zig 2024-11-07 21:45:10.459123650 +0100
|
--- exercises/102_formatting.zig 2026-03-20 19:23:48.873150100 +0100
|
||||||
+++ answers/099_formatting.zig 2024-11-07 21:43:55.154345991 +0100
|
+++ answers/102_formatting.zig 2026-04-02 10:51:15.799831421 +0200
|
||||||
@@ -131,7 +131,7 @@
|
@@ -131,7 +131,7 @@
|
||||||
for (0..size) |b| {
|
for (0..size) |b| {
|
||||||
// What formatting is needed here to make our columns
|
// What formatting is needed here to make our columns
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
--- exercises/102_testing.zig 2023-10-03 22:15:22.125574535 +0200
|
|
||||||
+++ answers/102_testing.zig 2023-10-05 20:04:07.302771500 +0200
|
|
||||||
@@ -83,7 +83,7 @@
|
|
||||||
// an error that you need
|
|
||||||
// to correct.
|
|
||||||
test "sub" {
|
|
||||||
- try testing.expect(sub(10, 5) == 6);
|
|
||||||
+ try testing.expect(sub(10, 5) == 5);
|
|
||||||
|
|
||||||
try testing.expect(sub(3, 1.5) == 1.5);
|
|
||||||
}
|
|
||||||
@@ -108,5 +108,5 @@
|
|
||||||
// Now we test if the function returns an error
|
|
||||||
// if we pass a zero as denominator.
|
|
||||||
// But which error needs to be tested?
|
|
||||||
- try testing.expectError(error.???, divide(15, 0));
|
|
||||||
+ try testing.expectError(error.DivisionByZero, divide(15, 0));
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
--- exercises/100_for4.zig 2023-10-03 22:15:22.125574535 +0200
|
--- exercises/103_for4.zig 2025-08-15 15:17:57.839348063 +0200
|
||||||
+++ answers/100_for4.zig 2023-10-05 20:04:07.296104707 +0200
|
+++ answers/103_for4.zig 2026-04-02 10:51:15.801831460 +0200
|
||||||
@@ -39,7 +39,7 @@
|
@@ -39,7 +39,7 @@
|
||||||
const hex_nums = [_]u8{ 0xb, 0x2a, 0x77 };
|
const hex_nums = [_]u8{ 0xb, 0x2a, 0x77 };
|
||||||
const dec_nums = [_]u8{ 11, 42, 119 };
|
const dec_nums = [_]u8{ 11, 42, 119 };
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
--- exercises/101_for5.zig 2023-10-03 22:15:22.125574535 +0200
|
--- exercises/104_for5.zig 2025-08-15 15:17:57.839348063 +0200
|
||||||
+++ answers/101_for5.zig 2023-10-05 20:04:07.299438103 +0200
|
+++ answers/104_for5.zig 2026-04-02 10:51:15.803831499 +0200
|
||||||
@@ -51,7 +51,7 @@
|
@@ -51,7 +51,7 @@
|
||||||
|
|
||||||
// We would like to number our list starting with 1, not 0.
|
// We would like to number our list starting with 1, not 0.
|
||||||
18
patches/patches/105_testing.patch
Normal file
18
patches/patches/105_testing.patch
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
--- exercises/105_testing.zig 2026-03-20 19:23:48.873150100 +0100
|
||||||
|
+++ answers/105_testing.zig 2026-04-02 10:51:15.805831538 +0200
|
||||||
|
@@ -71,7 +71,7 @@
|
||||||
|
// The corresponding test is not much different from the previous one. Except
|
||||||
|
// that it contains an error that you need to correct.
|
||||||
|
test "sub" {
|
||||||
|
- try testing.expect(sub(10, 5) == 6);
|
||||||
|
+ try testing.expect(sub(10, 5) == 5);
|
||||||
|
|
||||||
|
try testing.expect(sub(3, 1.5) == 1.5);
|
||||||
|
}
|
||||||
|
@@ -92,5 +92,5 @@
|
||||||
|
|
||||||
|
// Now we test if the function returns an error if we pass a zero as
|
||||||
|
// denominator. But which error needs to be tested?
|
||||||
|
- try testing.expectError(error.???, divide(15, 0));
|
||||||
|
+ try testing.expectError(error.DivisionByZero, divide(15, 0));
|
||||||
|
}
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
--- exercises/106_files.zig 2025-03-13 15:26:59.532367792 +0200
|
|
||||||
+++ answers/106_files.zig 2025-03-14 22:04:52.243435159 +0200
|
|
||||||
@@ -35,7 +35,7 @@
|
|
||||||
// by doing nothing
|
|
||||||
//
|
|
||||||
// we want to catch error.PathAlreadyExists and do nothing
|
|
||||||
- ??? => {},
|
|
||||||
+ error.PathAlreadyExists => {},
|
|
||||||
// if there's any other unexpected error we just propagate it through
|
|
||||||
else => return e,
|
|
||||||
};
|
|
||||||
@@ -44,7 +44,7 @@
|
|
||||||
// wait a minute...
|
|
||||||
// opening a directory might fail!
|
|
||||||
// what should we do here?
|
|
||||||
- var output_dir: std.fs.Dir = cwd.openDir("output", .{});
|
|
||||||
+ var output_dir: std.fs.Dir = try cwd.openDir("output", .{});
|
|
||||||
defer output_dir.close();
|
|
||||||
|
|
||||||
// we try to open the file `zigling.txt`,
|
|
||||||
@@ -55,7 +55,7 @@
|
|
||||||
// but here we are not yet done writing to the file
|
|
||||||
// if only there were a keyword in Zig that
|
|
||||||
// allowed you to "defer" code execution to the end of the scope...
|
|
||||||
- file.close();
|
|
||||||
+ defer file.close();
|
|
||||||
|
|
||||||
// you are not allowed to move these two lines above the file closing line!
|
|
||||||
const byte_written = try file.write("It's zigling time!");
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
--- exercises/103_tokenization.zig 2023-10-05 21:57:23.245974688 +0200
|
--- exercises/106_tokenization.zig 2026-03-20 19:23:48.873150100 +0100
|
||||||
+++ answers/103_tokenization.zig 2023-10-05 22:06:08.319119156 +0200
|
+++ answers/106_tokenization.zig 2026-04-02 10:51:15.807831578 +0200
|
||||||
@@ -136,7 +136,7 @@
|
@@ -134,7 +134,7 @@
|
||||||
;
|
;
|
||||||
|
|
||||||
// now the tokenizer, but what do we need here?
|
// now the tokenizer, but what do we need here?
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
--- exercises/104_threading.zig 2024-04-10 19:12:29.878856370 +0200
|
--- exercises/107_threading.zig 2026-04-01 23:31:10.073198955 +0200
|
||||||
+++ answers/104_threading.zig 2024-04-10 19:11:22.304265713 +0200
|
+++ answers/107_threading.zig 2026-04-02 10:51:15.809831617 +0200
|
||||||
@@ -97,12 +97,12 @@
|
@@ -88,12 +88,12 @@
|
||||||
defer handle.join();
|
defer handle.join();
|
||||||
|
|
||||||
// Second thread
|
// Second thread
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user