aboutsummaryrefslogtreecommitdiff
path: root/docs/notes/const_correctness.txt
blob: 2c745ac6975bdd3068b84f9333a846eb5e520f8d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163

const correctness
-----------------

TODO:  const/exclusive/readonly/restrict/volatile has been replaced:

    const     -->  var (default is const)
    exclusive -->  shared (default is exclusive)
    restrict/readonly/volatile has been removed. Use combinations of
    shared/var instead.

------


A variable of a type qualified with "const" may not be modified. For 
example:

    const int a = 4;
    a = 3; // Not allowed!

This is how to apply it to an array:

    const int[3] = [ 1, 2, 3 ];

It can also be placed on pointers, or values that pointers point to:

    int a = 1;
    int b = 2;
    
    // The variable that p points to may not be changed
    const int p^ = a;  
    p^ = 10; // Not allowed!
    p = @b;  // Ok
    
    // q may not be changed to point to something else
    int q const^ = a;
    q^ = 10; // Ok
    q = @b;  // Not allowed!

Const applies to entire structs, and by default also data that pointers 
in the structs point to:

    const (int x; int y) point = [3, 6];
    const ((int x; int y) from; (int x; int y) to) line = [[3,6], [1,2]];
    
    point.y = 5;     // Not allowed!
    line.from.x = 7; // Not allowed!

    
    int a = 1;
    const (int x; int^ p) something = [3, @a];
    
    something.a = 3;        // Not allowed!
    something.p = @point.x; // Not allowed!
    something.p^ = 2;       // Not allowed!


"transitive" const
------------------

The const keyword optionally takes a parameter, which means that the 
const qualifier is active only if the variable specified in the 
parameter has an active const qualifier.

It can be used with functions:

    const[s] zstring own[s]^ strstr(const zstring^ haystack;
                                    const zstring^ needle);
    
    int main() {
        
        zstring own^ hw = "Hello World";
        const zstring own^ dm = "Don't Modify";
        
        zstring^ a = strstr(hw, "World");
        const zstring^ b = strstr(dm, "Modify");
        zstring^ c = strstr(dm, "Modify"); // Not allowed!
        
        return 0;
    }


And structs:

    typedef Tree = (
        const[this] Tree own^? left, right;
        int value;
    );
    
    () main() {
        Tree a = [none, none, 23];
        const Tree root = [@a, none, 0];
        
        a.value = 1; // Ok
        root.left?^.value = 1; // Not allowed! (left has transitive const)
    }

readonly vs immutable vs exclusive
----------------------------------

Read only: May not be modified by "us", but could be modified from 
           somewhere else.

Immutable: It can be assumed that this variable is not modified.

Exclusive: May only be modified by "us".


conversion to immutable
-----------------------

    const int^ a = b;  // when is this allowed?

When may "pointer-to-immutable" types accept non-immutable pointers? In any
of these cases:

  * If we have an const or exclusive pointer which is known to point to the
    address (including any pointer we assign from). Then nobody else can
    have write access.
  * If we own the address, and we don't access the address as a writable
    object (through any pointer) after the assignment. The fact that the
    object is owned by us means that we know the presence of other pointers
    to it.


conversion to exclusive
-----------------------

Same rules as for conversion to immutable, except that it's not allowed to
have immutable pointers to the address.

exclusive access could be used in threaded applications:

    // Queue owns and has exclusive access to objects while they are in the
    // queue. Then it transfers them to anyone who calls dequeue.
    () enqueue[T](ParallelQueue[T]^ queue, exclusive A^ own obj);
    exclusive A^ own dequeue[T](ParallelQueue[T]^ queue);
    
    // Takes an object (and gets exclusive access and ownership) and calls a
    // callback with it as the parameter when an event occurs.
    () registerEvent(exclusive A own^ obj, ()(exclusive A^ obj) callback);


defaults
--------

  * const, readonly or mutable?
  * transient or not transient?

I think the default for variables in structs should be "transitive 
const", i.e. const[this], unless const or readonly is specified. Pointer 
destinations should similarly default to const[pointer]. const should 
imply readonly, and a "const" value should be type-compatible with a 
"readonly" variable.

const seems more useful than readonly, because most code is not going to 
work if the data it uses is being changed. But readonly can be useful if 
you store a pointer to something which might be changed, and in parallel 
code.

On the other hand, for most use cases it's probably easier for the 
(novice) programmer to only have readonly and not have to think about 
mutability. Except in some cases, such as parallel programs.