Skip to content

Commit dc8ce74

Browse files
authored
Merge pull request #44 from pedropark99/fix-c
Add section to describe type casting
2 parents 3eea7e3 + 147a49c commit dc8ce74

File tree

11 files changed

+231
-98
lines changed

11 files changed

+231
-98
lines changed

Chapters/03-structs.qmd

+89-6
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ if (x > 10) {
9797

9898

9999

100-
### Swith statements {#sec-switch}
100+
### Switch statements {#sec-switch}
101101

102102
Switch statements are also available in Zig.
103103
A switch statement in Zig have a similar syntax to a switch statement in Rust.
@@ -1043,11 +1043,8 @@ is commonly used on switch statements in Zig.
10431043
## Modules
10441044

10451045
We already talked about what modules are, and also, how to import other modules into
1046-
you current module through *import statements*, so that you can use functionality from these other modules in
1047-
your current module.
1048-
But in this section, I just want to make it clear that modules are actually structs in Zig.
1049-
1050-
In other words, every Zig module (i.e. a `.zig` file) that you write in your project
1046+
you current module through *import statements*.
1047+
Every Zig module (i.e. a `.zig` file) that you write in your project
10511048
is internally stored as a struct object.
10521049
Take the line exposed below as an example. In this line we are importing the
10531050
Zig Standard Library into our current module.
@@ -1089,3 +1086,89 @@ const pool = ThreadPool.init(
10891086
.{ .max_threads = num_threads }
10901087
);
10911088
```
1089+
1090+
1091+
1092+
## Type casting {#sec-type-cast}
1093+
1094+
In this section, I want to discuss type casting (or, type conversion) with you.
1095+
We use type casting when we have an object of type "x", and we want to convert
1096+
it into an object of type "y", i.e. we want to change the data type of the object.
1097+
1098+
Most languages have a formal way to perform type casting. In Rust for example, we normally
1099+
use the keyword `as`, and in C, we normally use the type casting syntax, e.g. `(int) x`.
1100+
In Zig, we use the `@as()` built-in function to cast an object of type "x", into
1101+
an object of type "y".
1102+
1103+
This `@as()` function is the preferred way to perform type conversion (or type casting)
1104+
in Zig. Because it is explicit, and, it also performs the casting only if it
1105+
is unambiguous and safe. To use this function, you just provide the target data type
1106+
in the first argument, and, the object that you want cast at the second argument.
1107+
1108+
```{zig}
1109+
#| auto_main: false
1110+
#| build_type: "test"
1111+
const std = @import("std");
1112+
const expect = std.testing.expect;
1113+
test {
1114+
const x: usize = 500;
1115+
const y = @as(u32, x);
1116+
try expect(@TypeOf(y) == u32);
1117+
}
1118+
```
1119+
1120+
This is the general way to perform type casting in Zig. But remember, `@as()` works only when casting
1121+
is unambiguous and safe, and there are situations where these assumptions do not hold. For example,
1122+
when casting an integer value into a float value, or vice-versa, it is not clear to the compiler
1123+
how to perform this conversion safely.
1124+
1125+
Therefore, we need to use specialized "casting functions" in such situations.
1126+
For example, if you want to cast an integer value into a float value, then, you
1127+
should use the `@floatFromInt()` function. In the inverse scenario, you should use
1128+
the `@intFromFloat()` function.
1129+
1130+
In these functions, you just provide the object that you want to
1131+
cast as input. Then, the target data type of the "type casting operation" is determined by
1132+
the type annotation of the object where you are saving the results.
1133+
In the example below, we are casting the object `x` into a value of type `f32`,
1134+
because the object `y`, which is where we are saving the results, is annotated
1135+
as an object of type `f32`.
1136+
1137+
```{zig}
1138+
#| auto_main: false
1139+
#| build_type: "test"
1140+
const std = @import("std");
1141+
const expect = std.testing.expect;
1142+
test {
1143+
const x: usize = 565;
1144+
const y: f32 = @floatFromInt(x);
1145+
try expect(@TypeOf(y) == f32);
1146+
}
1147+
```
1148+
1149+
Another built-in function that is very useful when performing type casting operations is `@ptrCast()`.
1150+
In essence, we use the `@as()` built-in function when we want to explicit convert (or cast) a Zig value/object
1151+
from a type "x" to a type "y", etc. However, pointers (we are going to discuss pointers
1152+
in more depth at @sec-pointer) are a special type of object in Zig,
1153+
i.e. they are treated differently from "normal objects".
1154+
1155+
Everytime a pointer is involved in some "type casting operation" in Zig, the `@ptrCast()` function is used.
1156+
This function works similarly to `@floatFromInt()`.
1157+
You just provide the pointer object that you want to cast as input to this function, and the
1158+
target data type is, once again, determined by the type annotation of the object where the results are being
1159+
stored.
1160+
1161+
```{zig}
1162+
#| auto_main: false
1163+
#| build_type: "test"
1164+
const std = @import("std");
1165+
const expect = std.testing.expect;
1166+
test {
1167+
const bytes align(@alignOf(u32)) = [_]u8{
1168+
0x12, 0x12, 0x12, 0x12
1169+
};
1170+
const u32_ptr: *const u32 = @ptrCast(&bytes);
1171+
try expect(@TypeOf(u32_ptr) == *const u32);
1172+
}
1173+
```
1174+

Chapters/13-image-filter.qmd

+20-22
Original file line numberDiff line numberDiff line change
@@ -285,8 +285,6 @@ object that reads the PNG file that we want to use.
285285
const c = @cImport({
286286
@cDefine("_NO_CRT_STDIO_INLINE", "1");
287287
@cInclude("stdio.h");
288-
});
289-
const png = @cImport({
290288
@cInclude("spng.h");
291289
});
292290
@@ -295,8 +293,8 @@ const file_descriptor = c.fopen(path, "rb");
295293
if (file_descriptor == null) {
296294
@panic("Could not open file!");
297295
}
298-
const ctx = png.spng_ctx_new(0) orelse unreachable;
299-
_ = png.spng_set_png_file(
296+
const ctx = c.spng_ctx_new(0) orelse unreachable;
297+
_ = c.spng_set_png_file(
300298
ctx, @ptrCast(file_descriptor)
301299
);
302300
```
@@ -334,13 +332,13 @@ Thus, an object of type `spng_ihdr` is a C struct that contains the data from th
334332
image header section of the PNG file.
335333

336334
Since this Zig function is receiving a C object (the `libspng` context object) as input, I marked
337-
the function argument `ctx` as "a pointer to the context object" (`*png.spng_ctx`), following the recommendations
335+
the function argument `ctx` as "a pointer to the context object" (`*c.spng_ctx`), following the recommendations
338336
that we have discussed at @sec-pass-c-structs.
339337

340338
```zig
341-
fn get_image_header(ctx: *png.spng_ctx) !png.spng_ihdr {
342-
var image_header: png.spng_ihdr = undefined;
343-
if (png.spng_get_ihdr(ctx, &image_header) != 0) {
339+
fn get_image_header(ctx: *c.spng_ctx) !c.spng_ihdr {
340+
var image_header: c.spng_ihdr = undefined;
341+
if (c.spng_get_ihdr(ctx, &image_header) != 0) {
344342
return error.CouldNotGetImageHeader;
345343
}
346344
@@ -375,10 +373,10 @@ the size of the space that we need to allocate.
375373

376374

377375
```zig
378-
fn calc_output_size(ctx: *png.spng_ctx) !u64 {
376+
fn calc_output_size(ctx: *c.spng_ctx) !u64 {
379377
var output_size: u64 = 0;
380-
const status = png.spng_decoded_image_size(
381-
ctx, png.SPNG_FMT_RGBA8, &output_size
378+
const status = c.spng_decoded_image_size(
379+
ctx, c.SPNG_FMT_RGBA8, &output_size
382380
);
383381
if (status != 0) {
384382
return error.CouldNotCalcOutputSize;
@@ -419,12 +417,12 @@ Also, we are using the `SPNG_FMT_RGBA8` enum value once again to inform the corr
419417
that the PNG image being decoded, uses the RGBA color model and 8 bit depth.
420418

421419
```zig
422-
fn read_data_to_buffer(ctx: *png.spng_ctx, buffer: []u8) !void {
423-
const status = png.spng_decode_image(
420+
fn read_data_to_buffer(ctx: *c.spng_ctx, buffer: []u8) !void {
421+
const status = c.spng_decode_image(
424422
ctx,
425423
buffer.ptr,
426424
buffer.len,
427-
png.SPNG_FMT_RGBA8,
425+
c.SPNG_FMT_RGBA8,
428426
0
429427
);
430428
@@ -579,26 +577,26 @@ in this new PNG file.
579577
580578
581579
```zig
582-
fn save_png(image_header: *png.spng_ihdr, buffer: []u8) !void {
580+
fn save_png(image_header: *c.spng_ihdr, buffer: []u8) !void {
583581
const path = "pedro_pascal_filter.png";
584582
const file_descriptor = c.fopen(path.ptr, "wb");
585583
if (file_descriptor == null) {
586584
return error.CouldNotOpenFile;
587585
}
588586
const ctx = (
589-
png.spng_ctx_new(png.SPNG_CTX_ENCODER)
587+
c.spng_ctx_new(c.SPNG_CTX_ENCODER)
590588
orelse unreachable
591589
);
592-
defer png.spng_ctx_free(ctx);
593-
_ = png.spng_set_png_file(ctx, @ptrCast(file_descriptor));
594-
_ = png.spng_set_ihdr(ctx, image_header);
590+
defer c.spng_ctx_free(ctx);
591+
_ = c.spng_set_png_file(ctx, @ptrCast(file_descriptor));
592+
_ = c.spng_set_ihdr(ctx, image_header);
595593
596-
const encode_status = png.spng_encode_image(
594+
const encode_status = c.spng_encode_image(
597595
ctx,
598596
buffer.ptr,
599597
buffer.len,
600-
png.SPNG_FMT_PNG,
601-
png.SPNG_ENCODE_FINALIZE
598+
c.SPNG_FMT_PNG,
599+
c.SPNG_ENCODE_FINALIZE
602600
);
603601
if (encode_status != 0) {
604602
return error.CouldNotEncodeImage;

Chapters/14-zig-c-interop.qmd

+5-7
Original file line numberDiff line numberDiff line change
@@ -417,14 +417,12 @@ As I described at the previous section, the `[*c]` portion of the type
417417
means that it is a C pointer. This strategy is not-recommended. But it is
418418
useful to demonstrate the use of `@ptrCast()`.
419419

420-
You may recall of the `@as()` built-in function, which is used to explicit convert (or cast) a Zig value from a type `x`
421-
to a type `y`, etc. That is, this `@as()` Zig function is equivalent to the
422-
`as` keyword in Rust, and the C type casting syntax (e.g. `(int) x`).
423-
But in our case here, we are not converting any type of object.
424-
More specifically, we are converting something into a pointer, or, a C pointer more specifically.
420+
You may recall of `@as()` and `@ptrCast()` from @sec-type-cast. Just as a recap,
421+
the `@as()` built-in function is used to explicit convert (or cast) a Zig value from a type "x"
422+
to a type "y".
423+
But in our case here, we are converting a pointer object, or, a C pointer more specifically.
425424
Everytime a pointer is involved in some "type casting operation" in Zig,
426-
the `@ptrCast()` function is involved. This `@ptrCast()` function is responsible
427-
for converting a pointer of one type to a pointer of another type.
425+
the `@ptrCast()` function is involved.
428426

429427
In the example below, we are using this function to cast our `path` object
430428
into a C pointer to an array of bytes. Then, we pass this C pointer as input

_freeze/Chapters/03-structs/execute-results/html.json

+5-3
Large diffs are not rendered by default.

_freeze/Chapters/13-image-filter/execute-results/html.json

+5-3
Large diffs are not rendered by default.

_freeze/Chapters/14-zig-c-interop/execute-results/html.json

+5-3
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)