@@ -522,8 +522,7 @@ not be authorized to call this method directly in my code.
522
522
523
523
524
524
525
-
526
- ## Anonymous struct literals {#sec-anonymous-struct-literals}
525
+ ### Anonymous struct literals {#sec-anonymous-struct-literals}
527
526
528
527
You can declare a struct object as a literal value. When we do that, we normally specify the
529
528
data type of this struct literal by writing it's data type just before the opening curly braces.
@@ -578,6 +577,231 @@ pub fn main() !void {
578
577
```
579
578
580
579
580
+
581
+ ### Struct declarations must be constant
582
+
583
+ Types in Zig must be ` const ` or ` comptime ` (we are going to talk more about comptime at @sec-comptime ).
584
+ What this means is that you cannot create a new data type, and mark it as variable with the ` var ` keyword.
585
+ So struct declarations are always constant. You cannot declare a new struct using the ` var ` keyword.
586
+ It must be ` const ` .
587
+
588
+ In the ` Vec3 ` example below, this declaration is allowed because I'm using the ` const ` keyword
589
+ to declare this new data type.
590
+
591
+ ``` {zig}
592
+ #| build_type: "lib"
593
+ #| auto_main: false
594
+ const Vec3 = struct {
595
+ x: f64,
596
+ y: f64,
597
+ z: f64,
598
+ };
599
+ ```
600
+
601
+
602
+ ### The ` self ` method argument
603
+
604
+ In every language that have OOP, when we declare a method of some class or struct, we
605
+ usually declare this method as a function that have a ` self ` argument.
606
+ This ` self ` argument is the reference to the object itself from which the method
607
+ is being called from.
608
+
609
+ Is not mandatory to use this ` self ` argument. But why would you not use this ` self ` argument?
610
+ There is no reason to not use it. Because the only way to get access to the data stored in the
611
+ data members of your struct is to access them through this ` self ` argument.
612
+ If you don't need to use the data in the data members of your struct inside your method, then, you very likely don't need
613
+ a method, you can just simply declare this logic as a simple function, outside of your
614
+ struct declaration.
615
+
616
+
617
+ Take the ` Vec3 ` struct below. Inside this ` Vec3 ` struct we declared a method named ` distance() ` .
618
+ This method calculates the distance between two ` Vec3 ` objects, by following the distance
619
+ formula in euclidean space. Notice that this ` distance() ` method takes two ` Vec3 ` objects
620
+ as input, ` self ` and ` other ` .
621
+
622
+
623
+ ``` {zig}
624
+ #| build_type: "lib"
625
+ #| auto_main: false
626
+ const std = @import("std");
627
+ const m = std.math;
628
+ const Vec3 = struct {
629
+ x: f64,
630
+ y: f64,
631
+ z: f64,
632
+
633
+ pub fn distance(self: Vec3, other: Vec3) f64 {
634
+ const xd = m.pow(f64, self.x - other.x, 2.0);
635
+ const yd = m.pow(f64, self.y - other.y, 2.0);
636
+ const zd = m.pow(f64, self.z - other.z, 2.0);
637
+ return m.sqrt(xd + yd + zd);
638
+ }
639
+ };
640
+ ```
641
+
642
+
643
+ The ` self ` argument corresponds to the ` Vec3 ` object from which this ` distance() ` method
644
+ is being called from. While the ` other ` is a separate ` Vec3 ` object that is given as input
645
+ to this method. In the example below, the ` self ` argument corresponds to the object
646
+ ` v1 ` , because the ` distance() ` method is being called from the ` v1 ` object,
647
+ while the ` other ` argument corresponds to the object ` v2 ` .
648
+
649
+
650
+ ``` {zig}
651
+ #| eval: false
652
+ const v1 = Vec3 {
653
+ .x = 4.2, .y = 2.4, .z = 0.9
654
+ };
655
+ const v2 = Vec3 {
656
+ .x = 5.1, .y = 5.6, .z = 1.6
657
+ };
658
+
659
+ std.debug.print(
660
+ "Distance: {d}\n",
661
+ .{v1.distance(v2)}
662
+ );
663
+ ```
664
+
665
+ ```
666
+ Distance: 3.3970575502926055
667
+ ```
668
+
669
+
670
+
671
+ ### About the struct state
672
+
673
+ Sometimes you don't need to care about the state of your struct object. Sometimes, you just need
674
+ to instantiate and use the objects, without altering their state. You can notice that when you have methods
675
+ inside this struct object that might use the values that are present the data members, but they
676
+ do not alter the values in the data members of structs in anyway.
677
+
678
+ The ` Vec3 ` struct that we presented in the previous section is an example of that.
679
+ This struct have a single method named ` distance() ` , and this method do use the values
680
+ present in all three data members of the struct (` x ` , ` y ` and ` z ` ). But at the same time,
681
+ this method do not change the values of these data members in any point.
682
+
683
+ As a result of that, when we create ` Vec3 ` objects we usually create them as
684
+ constant objects, like the ` v1 ` and ` v2 ` objects presented in the previous
685
+ code example. We can create them as variable objects with the ` var ` keyword,
686
+ if we want to. But because the methods of this ` Vec3 ` struct do not change
687
+ the state of the objects in any point, is unnecessary to mark them
688
+ as variable objects.
689
+
690
+ But why? Why am I talkin about this here? Is because the ` self ` argument
691
+ in the methods is affected depending on whether the
692
+ methods present in a struct change or not the state of the object itself.
693
+ More specifically, when you have a method in a struct that changes the state
694
+ of the object (i.e. change the value of a data member), the ` self ` argument
695
+ in this method must be annotated in a different manner.
696
+
697
+ As I described in the previous section, the ` self ` argument in methods of
698
+ a struct is the argument that receives as input the object from which the method
699
+ was called from. We usually annotate this argument in the methods by writing ` self ` ,
700
+ followed by the colon character (` : ` ), and the data type of the struct to which
701
+ the method belongs to (e.g. ` User ` , ` Vec3 ` , etc.).
702
+
703
+ If we take the ` Vec3 ` struct that we defined in the previous section as an example,
704
+ we can see in the ` distance() ` method that this ` self ` argument is annotated as
705
+ ` self: Vec3 ` . Because the state of the ` Vec3 ` object is never altered by this
706
+ method.
707
+
708
+ But what if we do have a method that alters the state of the object, by altering the
709
+ values of it's data members. How should we annotate ` self ` in this instance? The answer is:
710
+ "we should pass a pointer of ` x ` to ` self ` , and not simply a copy of ` x ` to ` self ` ".
711
+ In other words, you should annotate ` self ` as ` self: *x ` , instead of annotating it
712
+ as ` self: x ` .
713
+
714
+ If we create a new method inside the ` Vec3 ` object that, for example, expands the
715
+ vector by multiplying it's coordinates by a factor o two, then, we need to follow
716
+ this rule specified in the previous paragraph. The code example below demonstrates
717
+ this idea:
718
+
719
+ ``` {zig}
720
+ #| build_type: "lib"
721
+ #| auto_main: false
722
+ const std = @import("std");
723
+ const m = std.math;
724
+ const Vec3 = struct {
725
+ x: f64,
726
+ y: f64,
727
+ z: f64,
728
+
729
+ pub fn distance(self: Vec3, other: Vec3) f64 {
730
+ const xd = m.pow(f64, self.x - other.x, 2.0);
731
+ const yd = m.pow(f64, self.y - other.y, 2.0);
732
+ const zd = m.pow(f64, self.z - other.z, 2.0);
733
+ return m.sqrt(xd + yd + zd);
734
+ }
735
+
736
+ pub fn double(self: *Vec3) void {
737
+ self.x = self.x * 2.0;
738
+ self.y = self.y * 2.0;
739
+ self.z = self.z * 2.0;
740
+ }
741
+ };
742
+ ```
743
+
744
+ Notice in the code example above that we have added a new method
745
+ to our ` Vec3 ` struct named ` double() ` . This method essentially doubles the
746
+ coordinate values of our vector object. Also notice that, in the
747
+ case of the ` double() ` method, we annotated the ` self ` argument as ` *Vec3 ` ,
748
+ indicating that this argument receives a pointer (or a reference, if you prefer to call it this way)
749
+ to a ` Vec3 ` object, instead of receiving a copy of the object directly, as input.
750
+
751
+ ``` {zig}
752
+ #| eval: false
753
+ var v3 = Vec3 {
754
+ .x = 4.2, .y = 2.4, .z = 0.9
755
+ };
756
+ v3.double();
757
+ std.debug.print("Doubled: {d}\n", .{v3.x});
758
+ ```
759
+
760
+ ```
761
+ Doubled: 8.4
762
+ ```
763
+
764
+
765
+
766
+ If you change the ` self ` argument in this ` double() ` method to ` self: Vec3 ` , like in the
767
+ ` distance() ` method, you will get the error exposed below as result. Notice that this
768
+ error message is indicating a line from the ` double() ` method body,
769
+ indicating that you cannot alter the value of the ` x ` data member.
770
+
771
+ ``` zig
772
+ // If we change the function signature of double to:
773
+ pub fn double(self: Vec3) void {
774
+ ```
775
+
776
+ This error message indicates that the ` x ` data member belongs to a constant object,
777
+ and, because of that, it cannot be changed. Even though we marked the ` v3 ` object
778
+ that we have created in the previous code example as a variable object.
779
+ So even though this ` x ` data member belongs to a variable object in our code, this error
780
+ message is pointing to the opposite direction.
781
+
782
+ ```
783
+ t.zig:16:13: error: cannot assign to constant
784
+ self.x = self.x * 2.0;
785
+ ~~~~^~
786
+ ```
787
+
788
+ But this error message is misleading, because the only thing that we have changed
789
+ is the ` self ` argument signature from ` *Vec3 ` to ` Vec3 ` in the ` double() ` method. So, just remember this
790
+ general rule below about your method declarations:
791
+
792
+ ::: {.callout-note}
793
+ If a method of your ` x ` struct alters the state of the object, by
794
+ changing the value of any data member, then, remember to use ` self: *x ` ,
795
+ instead of ` self: x ` in the function signature of this method.
796
+ :::
797
+
798
+ You could also interpret the content discussed in this section as:
799
+ "if you need to alter the state of your ` x ` struct object in one of it's methods,
800
+ you must pass the ` x ` struct object by reference to the ` self ` argument of this method,
801
+ instead of passing it by value".
802
+
803
+
804
+
581
805
## Type inference {#sec-type-inference}
582
806
583
807
Zig is kind of a strongly typed language. I say "kind of" because there are situations
0 commit comments