You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -108,44 +108,150 @@ There are three primary uses of the ``const`` keyword proposed here:
108
108
* On attributes and fields classes
109
109
* On variables
110
110
111
-
On function arguments
112
-
---------------------
111
+
Function Arguments
112
+
------------------
113
+
114
+
An argument marked as ``const`` (whether a positional argument or a keyword argument) functions exactly as if a local variable were defined at the top of the function as ``const``.
115
+
116
+
Key behaviors include:
113
117
114
-
An argument marked as ``const``, be it an arg or a kwarg, functions exactly as if you were to define a local variable at the top of the function as ``const``. It cannot be modified, and the object it refers to cannot be updated or written to in any way. It can only be passed to functions that also expect it as "``const``" - that is, you cannot erase ``const``ness once it is applied. It can be copied out to a non ``const`` variable, and that is the proposed analogue of ``const_cast`` here, the only way to un-``const`` something is via a copy.
118
+
* **Immutability**: The variable cannot be modified, and the object it refers to cannot be updated or written to in any way.
119
+
* **Transitive Constness**: A ``const`` argument can only be passed to other functions that also expect it as ``const``. You cannot erase "constness" once it is applied.
120
+
* **Explicit Copying**: It can be copied out to a non-``const`` variable. This is the proposed analogue to C++'s ``const_cast``; the only way to "un-const" something is via a copy.
115
121
116
-
Shadowing a name becomes an exception.
122
+
Reassignment and Shadowing
123
+
^^^^^^^^^^^^^^^^^^^^^^^^^^
124
+
125
+
126
+
Shadowing or reassigning a ``const`` name is treated as an exception.
117
127
118
128
.. code-block:: python
119
129
120
-
deffoo(const bar, baz):
121
-
bar =3# Fails, raises on shadowing
122
-
return bar * baz
130
+
deffoo(const bar, baz):
131
+
bar =3# Fails, raises on reassignment/shadowing
132
+
return bar * baz
133
+
134
+
When passing a ``const`` variable to another function, the receiving function's arguments must also be marked ``const``.
123
135
124
136
.. code-block:: python
125
137
126
-
defboo(bat, bat):
127
-
...
138
+
# Standard function with mutable arguments
139
+
defboo(bat, man):
140
+
...
128
141
129
-
deffoo(const bar, baz):
130
-
boo(bar, bar) # Fails, raises on passing bar to boo's bat, which is not `const`
131
-
...
142
+
deffoo(const bar, baz):
143
+
boo(bar, baz) # Fails: raises on passing 'bar' to boo's 'bat',
144
+
# because 'bat' is not marked 'const'.
145
+
...
132
146
133
-
On attributes and fields in classes
134
-
-----------------------------------
147
+
Class Attributes and Fields
148
+
---------------------------
135
149
136
-
This makes the attribute only writable at __init__ time - or assignable with a default. It is illegal to modify a ``const`` variable after.
150
+
Marking an attribute as ``const`` makes it writable only at ``__init__`` time (or assignable via a default value). It is illegal to modify a ``const`` attribute after initialization.
137
151
138
152
.. code-block:: python
139
153
140
-
classMyWidget:
141
-
const x: int
154
+
classMyWidget:
155
+
const x: int
142
156
143
-
defupdate(self, x):
144
-
self.x = x # Fails, always raises, x is const
157
+
defupdate(self, x):
158
+
self.x = x # Fails: always raises as 'self.x' is const
159
+
160
+
Variables
161
+
---------
162
+
163
+
As covered in previous sections, both local and global variables can be declared ``const``. This enforces the renaming and update semantics described above.
164
+
165
+
Critically, these variables can only be passed to functions where the corresponding argument is also marked ``const``.
166
+
167
+
Benefits
168
+
========
169
+
170
+
Compiler Benefits
171
+
-----------------
172
+
173
+
Globals Optimization
174
+
^^^^^^^^^^^^^^^^^^^^
175
+
176
+
If the compiler knows a global is ``const``, it can bake its value directly into the bytecode of functions that use it, rather than emitting ``LOAD_GLOBAL`` instructions.
177
+
178
+
Consider the following standard Python code:
179
+
180
+
.. code-block:: python
181
+
182
+
DEBUG=False
183
+
deffoo():
184
+
ifDEBUG:
185
+
...
186
+
ifDEBUG:
187
+
...
188
+
189
+
Currently, this results in repeated ``LOAD_GLOBAL`` instructions and runtime checks:
190
+
191
+
.. code-block:: text
192
+
193
+
Disassembly of <code object foo at 0x561367afd590, file "example.py", line 2>:
194
+
2 RESUME 0
195
+
196
+
3 LOAD_GLOBAL 0 (DEBUG)
197
+
TO_BOOL
198
+
POP_JUMP_IF_FALSE 1 (to L1)
199
+
200
+
4 NOP
201
+
202
+
5 L1: LOAD_GLOBAL 0 (DEBUG)
203
+
TO_BOOL
204
+
POP_JUMP_IF_FALSE 1 (to L2)
205
+
206
+
6 RETURN_CONST 0 (None)
207
+
208
+
5 L2: RETURN_CONST 0 (None)
209
+
210
+
With a ``const`` global, the compiler can store the value once, skip the ``LOAD_GLOBAL`` opcodes, and potentially use static analysis to identify and remove the dead branches entirely.
211
+
212
+
Class Safety and MRO Optimization
213
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
214
+
If a class method is marked ``const``, the compiler guarantees it will never be overridden by a subclass or shadowed by an instance attribute.
215
+
216
+
When calling ``my_obj.const_method()``, the compiler does not need to check the instance dictionary or walk the Method Resolution Order (MRO). It can compile a direct call to that exact function object.
217
+
218
+
JIT Guard Reduction
219
+
~~~~~~~~~~~~~~~~~~~
220
+
JIT compilers that rely on guards (such as CPython's JIT, torchdynamo, etc.) can emit fewer guards, as the invariants provided by ``const`` reduce the number of state changes that need monitoring.
221
+
222
+
Non-Compiler Benefits
223
+
---------------------
224
+
225
+
* **Readability**: Code becomes cleaner and easier to reason about.
226
+
* **Invariants**: Provides stronger invariants at the language level, reducing classes of bugs related to accidental mutation.
227
+
228
+
229
+
Back-compat
230
+
===========
231
+
232
+
Should be entirely sound - except for cases where someone is using ``const`` as a variable name. This should become a SyntaxError, which should be relatively trivial to fix, and can be detected entirely statically (linting, etc).
233
+
234
+
235
+
Implementation / Open questions
236
+
===============================
237
+
238
+
Note - this section needs further exploration and is a WIP.
239
+
240
+
Basics
241
+
------
242
+
The implementation would require adding const to the python grammar, updating the ast, and all other language level structures that handle keywords.
243
+
244
+
Less restrictive / phase 1
245
+
--------------------------
246
+
The first phase, rebinding, (or, breaking rebinding, aka, the final keyword like work described above) - seems relatively straightforward. Bytecodes used for assignment (store_fast, etc) - would be extended to look up our const tagging and fail according to the descriptions above.
247
+
248
+
249
+
Frozen objects / phase 2
250
+
------------------------
251
+
For the second phase, more akin to a frozen object, we would need to come up with new bytecodes that set flags that propagate the constness of the object to the underlying implementation. I think we would start with builtin types (PyList, PyDict) and start exploring intercessions into functions like PyList_Append to respect the constness of the object.
145
252
146
-
On variables
147
-
------------
148
253
149
-
Mostly covered above, but either a local or global can be declared ``const``, and enforces renaming and update semantics described above.
254
+
Viral constness / phase 3
255
+
-------------------------
256
+
Viral constness seems tricky to implement, as it would incur a type check on every function call with const keywords in it.
150
257
151
-
Can only be passed functions where the argument is marked ``const``.
0 commit comments