I started programming in Rust quite loads of years prior to now, and it has gradually changed the model I create
applications in other programming languages, most notably in Python. Sooner than I started the insist of Rust, I was
most often writing Python code in an extraordinarily dynamic and kind-free arrangement, with out kind hints, passing and
returning dictionaries in each field, and now and as soon as more falling support
to “stringly-typed” interfaces. On the other hand, after
experiencing the strictness of the Rust kind scheme, and noticing the full concerns that it
prevents “by construction”, I became somewhat anxious every time I purchased support to Python and
wasn’t supplied with the identical ensures.
To be certain, by “ensures” I don’t imply memory safety right here (Python within reason memory actual
as-is), however somewhat “soundness” – the conception that of designing APIs which could per chance well well be very laborious or outright now not seemingly
to misuse and thus prevent undefined behaviour and assorted bugs. In Rust, an incorrectly frail
interface will most often aim a compilation error. In Python, that you just can peaceful attain such unsuitable
program, however must you insist a sort checker (like pyright
) or
an IDE with a sort analyzer (like PyCharm), that you just can peaceful salvage a identical level of speedy strategies about a
imaginable inform.
In the extinguish, I started adopting some ideas from Rust in my Python applications.
It most often boils all of the model down to two issues – the insist of kind hints as mighty as imaginable, and upholding
the factual ol’
making unlawful states unrepresentable
principle. I strive to attach this every for applications that will be maintained for a while, however additionally for oneshot
utility scripts. Mostly on narrative of in my skills, the latter somewhat often change into the old model 🙂 In
my skills, this arrangement ends in applications which could per chance well well be more uncomplicated to stamp and alternate.
In this put up I’ll philosophize just a few examples of such patterns applied to Python
applications. It’s no rocket science, however I peaceful felt love it must also very well be precious to file them.
Divulge: this put up comprises a technique of opinions about writing Python code. I don’t desire so that you just would possibly want to per chance add
“IMHO” to every sentence, so get every part on this put up as simply my opinions on the matter,
somewhat than makes an strive to promote some original truths 🙂 Furthermore, I’m now not claiming that the
supplied tips had been all invented in Rust, they are additionally frail in other languages, after all.
The indispensable and valuable ingredient is the insist of kind hints the build aside imaginable, in particular in characteristic
signatures and class attributes. After I learn a characteristic signature having a stare like this:
def find_item(data, check):
I haven’t any conception what’s going on from the signature itself. Is data
a record, a dict or a
database connection? Is check
a boolean, or a characteristic? What does this
characteristic return? What occurs if it fails, does it elevate an exception, or return None
? To search out
solutions to these questions, I either desire to head learn the characteristic body (and often recursively
the bodies of other capabilities it calls – that is somewhat anxious), or learn its documentation (if there could be any).
While the documentation could per chance well also personal precious data about what the characteristic does, it shouldn’t be
the largest to additionally insist it for documenting solutions to the outdated questions. Rather just a few them will be
answered by a constructed-in mechanism – kind hints.
def find_item(
data: List[Item],
check: Callable[[Item], bool]
) -> No longer compulsory[Item]:
Did it get me more time to write the signature? Yes. Is that a inform? No, except my coding
is bottlenecked by the form of characters I write per minute, and that doesn’t truly happen.
Writing out the types explicitly forces me to judge about what’s going to be the particular interface supplied
by the characteristic, and the arrangement in which can I personal it as strict as imaginable, to personal it laborious for its callers to
insist it in a corrupt arrangement. With the signature above, I’m in a position to salvage a sexy factual conception how can I insist the
characteristic, what to cross it as arguments, and what can I ask to be returned from it. Furthermore,
unlike a doc inform, which can salvage with out difficulty outdated when the code adjustments, after I alternate the types
and don’t update the callers of the characteristic, the typechecker will shout at me1. And if I’m enthusiastic
in what’s Item
, I’m in a position to factual insist Hurry to definition
and straight stare how does that kind stare like.
Now, I’m now not a sith absolutist on this regard, and if it takes five nested kind hints to
characterize a single parameter, I will most often factual stop and give it a more uncomplicated, albeit imprecise
kind. In my skills, this field does now not happen that often. And if it does happen, it must
truly value a inform with the code – if your characteristic parameter will be a number, a tuple of
strings or a dictionary mapping strings to integers, it could well per chance well even be a value that you just would possibly want to per chance per chance well also desire to
refactor and simplify it.
The insist of kind hints is one ingredient, however that merely describes what’s the interface of your capabilities.
The 2nd step is de facto making these interfaces as accurate and “locked down” as imaginable. A same old
example is returning just a few values (or a single complicated value) from a characteristic. The lazy and
speedy arrangement is to come support a tuple:
def find_person(...) -> Tuple[str, str, int]:
Gigantic, we know that we’re returning three values. What are they? Is the indispensable string the indispensable title
of the person? The 2nd string the surname? What’s the number? Is it age? Achieve in some checklist?
Social security number? This roughly typing is opaque and except you stare into the characteristic body,
you don’t know what occurs right here.
The subsequent step to “toughen” this could per chance per chance well also very well be to come support a dictionary:
def find_person(...) -> Dict[str, Any]:
...
return {
"title": ...,
"city": ...,
"age": ...
}
Now we even comprise an conception what are the person returned attributes, however we as soon as more desire to
stare the characteristic body to search out out. In a technique, the style bought even worse, on narrative of now
we don’t even know the depend and the varieties of the person attributes. Furthermore, when this
characteristic adjustments and the keys in the returned dictionary are renamed or removed, there’s no straightforward approach to search out
out with a typechecker, and thus its callers most often desire to be changed with an extraordinarily handbook and
anxious urge-fracture-regulate code cycle.
The upright resolution is to come support a strongly typed object with named parameters that comprise an
hooked up kind. In Python, this implies we now desire to compose a class. I believe that tuples and dicts
are frail so often in these scenarios on narrative of it’s factual so mighty more uncomplicated than to account for a class (and judge of
a title for it), compose a constructor with parameters, store the parameters into fields and so on.
Since Python 3.7 (and sooner with a equipment polyfill), there could be a mighty sooner resolution – dataclasses
.
@dataclasses.dataclass
class City:
title: str
zip_code: int
@dataclasses.dataclass
class Person:
title: str
city: City
age: int
def find_person(...) -> Person:
You continue to desire to take into narrative a title for the created class,
however rather than that, it’s gorgeous mighty as concise as it must salvage, and also you salvage kind annotations for all attributes.
With this dataclass, I comprise an explicit description of what the characteristic returns. After I name
this characteristic and work with the returned value, IDE autocompletion will philosophize me what are the
names and varieties of its attributes. This could occasionally likely per chance well also sound trivial, however for me it is a long way a colossal productivity earnings.
Furthermore, when the code is refactored, and the attributes alternate, my IDE and the
typechecker will shout at me and philosophize me all locations that desire to be changed, with out me having
to attain this system in any admire. For some straightforward refactorings (e.g. attribute rename), the IDE also can personal
these adjustments for me. As well, with explicitly named kinds, I’m in a position to invent a vocabulary of terms (Person
, City
)
that I’m in a position to then part with other capabilities and classes.
The one ingredient from Rust that I potentially lack basically the most in most mainstream languages are algebraic data
kinds (ADTs)2. It’s a long way an extremely powerful tool to explicitly characterize the shapes of info
my code is working with. To illustrate, after I’m working with packets in Rust, I’m in a position to explicitly
enumerate the full a technique of varieties of packets that will be bought, and fix various data (fields) to every
of them:
enum Packet {
Header {
protocol: Protocol,
dimension: usize
},
Payload {
data: Vec<u8>
},
Trailer {
data: Vec<u8>,
checksum: usize
}
}
And with sample matching, I’m in a position to then react to the person variants, and the compiler assessments that I don’t cross over
any cases:
fn handle_packet(packet: Packet) {
match packet {
Packet:: Header { protocol, dimension } => ...,
Packet:: Payload { data } |
Packet:: Trailer { data, ...} => println!("{data:?}")
}
}
That is value it for making sure that invalid states are now not representable and thus avoiding many runtime errors.
ADTs are in particular precious in statically typed languages,
the build aside must you desire to work with a region of kinds in an unified components, you will desire a shared “title” with which
you are going to consult with them. Without ADTs, that is every now and as soon as more carried out the insist of OOP interfaces and/or inheritance.
Interfaces and digital programs comprise their field when the region of frail kinds is launch-ended, then as soon as more when the region
of kinds is closed, and also you desire to make breeze you handle the full imaginable variants, ADTs and sample
matching is a mighty better match.
In a dynamically typed language, akin to Python, there’s now not truly a desire to comprise a shared title for a region of kinds,
mainly on narrative of you don’t even desire to title the types frail on this system in the indispensable field. On the other hand, it must peaceful be
precious to make insist of something akin to ADTs, by creating a union kind:
@dataclass
class Header:
protocol: Protocol
dimension: int
@dataclass
class Payload:
data: str
@dataclass
class Trailer:
data: str
checksum: int
Packet = typing.Union[Header, Payload, Trailer]
# or `Packet=Header | Payload | Trailer` since Python 3.10
Packet
right here defines a brand recent kind, that will be either a header, a payload or a trailer packet. I’m in a position to now insist
this kind (title) in the relaxation of my program after I desire to ensure only these three classes will be worthwhile.
Divulge that there could be now not a explicit “ticket” hooked as much as the classes, so when we desire to differentiate them, we now desire to make insist of
e.g. instanceof
or sample matching:
def handle_is_instance(packet: Packet):
if isinstance(packet, Header):
print("header {packet.protocol} {packet.dimension}")
elif isinstance(packet, Payload):
print("payload {packet.data}")
elif isinstance(packet, Trailer):
print("trailer {packet.checksum} {packet.data}")
else:
enlighten Deceptive
def handle_pattern_matching(packet: Packet):
match packet:
case Header(protocol, dimension): print(f"header {protocol} {dimension}")
case Payload(data): print("payload {data}")
case Trailer(data, checksum): print(f"trailer {checksum} {data}")
case _: enlighten Deceptive
Sadly, right here we now desire to (or somewhat, could per chance well also peaceful) consist of the traumatic enlighten Deceptive
branches so that the characteristic crashes
when it receives sudden data. In Rust, this could per chance per chance well be a compile-time error in its build aside.
Divulge: A total lot of folks on Reddit comprise stroke a chord in my memory that
enlighten Deceptive
is de facto optimized away entirely
in optimized invent (python -O ...
). Thus it could well per chance well even be safer to enhance an exception straight.
There could be additionallytyping.assert_never
from Python 3.11, which explicitly tells a sort checker that falling to this branch desires to be a “compile-time”
error.
A edifying property of the union kind is that it is outlined exterior the class that is phase of the union.
The class subsequently does now not know that it is being integrated in the union, which reduces coupling in code.
And you also can compose just a few various unions the insist of the identical kind:
Packet = Header | Payload | Trailer
PacketWithData = Payload | Trailer
Union kinds are additionally somewhat precious for automatic (de)serialization. Lately I came upon an astronomical serialization
library known as pyserde, which is per the earlier Rust
serde serialization framework. Amongst many other wintry capabilities, it is ready to
leverage typing annotations to serialize and deserialize union kinds with out any extra code:
import serde
...
Packet = Header | Payload | Trailer
@dataclass
class Data:
packet: Packet
serialized = serde.to_dict(Data(packet=Trailer(data="foo", checksum=42)))
# {'packet': {'Trailer': {'data': 'foo', 'checksum': 42}}}
deserialized = serde.from_dict(Data, serialized)
# Data(packet=Trailer(data='foo', checksum=42))
It’s good to per chance well also even get how will the union ticket be
serialized, same as with serde
. I was hunting for identical efficiency for an extraordinarily very long time, on narrative of it’s
somewhat precious to (de)serialize union kinds. On the other hand, it used to be somewhat anxious to place into effect it in most
other serialization libraries that I attempted (e.g. dataclasses_json
or dacite
).
To illustrate, when working with machine studying items, I’m the insist of unions to store assorted kinds
of neural networks (e.g. a classification or a segmentation CNN items) inner a single config file structure.
I comprise additionally came upon it precious to model various codecs of info (in my case configuration recordsdata), like this:
Config = ConfigV1 | ConfigV2 | ConfigV3
By deserializing Config
, I’m ready to learn all outdated variations of the config structure, and thus take backwards
compatibility.
In Rust, it is somewhat original to account for data kinds that attain now not add any recent habits, however attend
simply to specify the area and intended utilization of some other, otherwise somewhat total data kind
– shall we embrace integers. This sample is called a “newtype” 3 and it could well per chance well even be additionally frail in Python.
Right here’s a motivating example:
class Database:
def get_car_id(self, model: str) -> int:
def get_driver_id(self, title: str) -> int:
def get_ride_info(self, car_id: int, driver_id: int) -> RideInfo:
db = Database()
car_id = db.get_car_id("Mazda")
driver_id = db.get_driver_id("Stig")
info = db.get_ride_info(driver_id, car_id)
Situation the error?
…
…
The arguments for get_ride_info
are swapped. There could be now not any kind error, on narrative of every automobile IDs
and driver IDs are simply integers, subsequently the types are factual, although semantically
the characteristic name is corrupt.
We can solve this inform by defining separate kinds for various varieties of IDs with a “NewType”:
from typing import NewType
# Provide an explanation for a brand recent kind known as "CarId", which is internally an `int`
CarId = NewType("CarId", int)
# Ditto for "DriverId"
DriverId = NewType("DriverId", int)
class Database:
def get_car_id(self, model: str) -> CarId:
def get_driver_id(self, title: str) -> DriverId:
def get_ride_info(self, car_id: CarId, driver_id: DriverId) -> RideInfo:
db = Database()
car_id = db.get_car_id("Mazda")
driver_id = db.get_driver_id("Stig")
# Form error right here -> DriverId frail in its build aside of CarId and vice-versa
info = db.get_ride_info(<error>driver_iderror>, <error>car_iderror>)
That is a somewhat straightforward sample that can per chance well support remove errors which could per chance well well be otherwise laborious to position. It’s a long way
in particular precious e.g. must you’re facing a technique of various varieties of IDs (CarId
vs DriverId
)
or with some metrics (Slip
vs Size
vs Temperature
and so on.) that must now not be blended together.
One ingredient that I somewhat like about Rust is that it doesn’t comprise constructors per se. As a replace,
folks tend to make insist of same old capabilities to compose (ideally well initialized) cases of
structs. In Python, there could be now not a constructor overloading, subsequently must you would possibly want to per chance per chance well also desire to make an object
in just a few ways, every now and as soon as more this ends in an __init__
arrangement that has a technique of parameters which attend
for initialization in various ways, and which can now not truly be frail together.
As a replace, I grab to compose “construction” capabilities with an explicit title that makes it evident the model to
make the article and from which data:
class Rectangle:
@staticmethod
def from_x1x2y1y2(x1: recede with the waft, ...) -> "Rectangle":
@staticmethod
def from_tl_and_size(high: recede with the waft, left: recede with the waft, width: recede with the waft, high: recede with the waft) -> "Rectangle":
This makes it mighty cleaner to make the article, and doesn’t allow users of the class to cross invalid
data when developing the article (e.g. by combining y1
and width
).
The insist of the style scheme itself to encode invariants that can per chance well otherwise be only tracked at runtime is an extraordinarily
total and robust conception. In Python (however additionally other mainstream languages), I often stare classes
which could per chance well well be colossal furry balls of mutable notify. Concept to be some of the sources of this mess is code that tries to trace
the article’s invariants at runtime. It has to take in tips many scenarios
that can per chance well happen in conception, on narrative of they weren’t made now not seemingly by the style scheme (“what if the
client has been requested to disconnect, and now somebody tries to send a message to it, however the socket
is peaceful linked” and so on.).
Consumer
Right here’s a same old example:
class Consumer:
"""
Principles:
- Enact now not name `send_message` outdated to calling `connect` and then `authenticate`.
- Enact now not name `connect` or `authenticate` just a few times.
- Enact now not name `shut` with out calling `connect`.
- Enact now not name any arrangement after calling `shut`.
"""
def __init__(self, take care of: str):
def connect(self):
def authenticate(self, password: str):
def send_message(self, msg: str):
def shut(self):
…straightforward, factual? You factual desire to carefully learn the documentation, and be certain you never
ruin the mentioned tips (lest you invoke either undefined behaviour or a fracture). An different is to
enjoy the class with assorted asserts that check the full mentioned tips at runtime, which ends in messy code,
neglected edge cases and slower strategies when something is corrupt (compile-time vs urge-time).
The core of the inform is that the client can exist in assorted (mutually irregular) states, however in its build aside
of modelling these states individually, they are all merged in a single kind.
Let’s stare if we can toughen this by splitting the a technique of states into separate kinds 4.
- First of all, does it even personal sense to comprise a
Consumer
which isn’t linked to anything?
Doesn’t appear to be so. Such an unconnected client can’t attain anything until you nameconnect
anyway. So why allow this notify to exist in any admire? We can compose a construction characteristic known as
connect
that can return a linked client:
def connect(take care of: str) -> No longer compulsory[ConnectedClient]:
cross
class ConnectedClient:
def authenticate(...):
def send_message(...):
def shut(...):
If the characteristic succeeds, this could occasionally return a shopper that upholds the “is hooked up” invariant, and on which
that you just can now not name connect
as soon as more to clutter issues up. If the connection fails, the characteristic can elevate an exception
or return None
or some explicit error.
- A identical arrangement will be frail for the
authenticated
notify. We can introduce one other kind,
which holds the invariant that the client is every linked and authenticated:
class ConnectedClient:
def authenticate(...) -> No longer compulsory["AuthenticatedClient"]:
class AuthenticatedClient:
def send_message(...):
def shut(...):
Simplest when we even comprise an occasion of an AuthenticatedClient
, then we can truly commence sending messages.
- The last inform is with the
shut
arrangement. In Rust (on narrative of of
unfavorable cross semantics), we are ready to categorical the truth
that when theshut
arrangement is called, that you just can now not insist the client anymore. That is now not truly imaginable in Python,
so we now desire to make insist of some workaround. One resolution could per chance well also very well be to tumble support to runtime tracking, introduce a boolean
attribute in the client, and enlighten inshut
andsend_message
that it hasn’t been closed already.
One other arrangement could per chance well also very well be to get theshut
arrangement entirely and factual insist the client as a context manager:
with connect(...) as client:
client.send_message("foo")
# Right here the client is closed
With out a shut
arrangement available, that you just can now not shut the client twice unintentionally5.
Strongly-typed bounding containers
Object detection is a pc imaginative and prescient job that I every now and as soon as more work on, the build aside a program has to detect a region of bounding containers
in an image. Bounding containers are most often glorified rectangles with some hooked up data, and must you put into effect object
detection, they are in each field. One anxious ingredient about them is that every now and as soon as more they are normalized (the coordinates and sizes of the rectangle are
in the interval [0.0, 1.0]
), however every now and as soon as more they are denormalized (the coordinates and sizes are bounded by the scale
of the image they are hooked as much as). Whilst you happen to send a bounding box through many capabilities that handle e.g. data
preprocessing or postprocessing, it is straightforward to clutter this up, and e.g. normalize a bounding box twice, which ends in
errors which could per chance well well be somewhat anxious to debug.
This has occurred to me as soon as or twice, so one time I decided to solve this for factual by splitting these two kinds
of bboxes into two separate kinds:
@dataclass
class NormalizedBBox:
left: recede with the waft
high: recede with the waft
width: recede with the waft
high: recede with the waft
@dataclass
class DenormalizedBBox:
left: recede with the waft
high: recede with the waft
width: recede with the waft
high: recede with the waft
With this separation, normalized and denormalized bounding containers can now not be with out difficulty blended together anymore, which
principally solves the inform. On the other hand, there are some enhancements that we can personal to personal the code more ergonomic:
- Decrease duplication, either by composition or inheritance:
@dataclass
class BBoxBase:
left: recede with the waft
high: recede with the waft
width: recede with the waft
high: recede with the waft
# Composition
class NormalizedBBox:
bbox: BBoxBase
class DenormalizedBBox:
bbox: BBoxBase
Bbox = Union[NormalizedBBox, DenormalizedBBox]
# Inheritance
class NormalizedBBox(BBoxBase):
class DenormalizedBBox(BBoxBase):
- Add a runtime check to ensure the normalized bounding box is de facto normalized:
class NormalizedBBox(BboxBase):
def __post_init__(self):
enlighten 0.0 <= self.left <= 1.0
...
- Add a way of converting between the two representations. In some places, we might want to know the explicit
representation, but in others, we want to work with a generic interface (“any type of BBox”). In that case we should
be able to convert “any BBox” to one of the two representations:
class BBoxBase:
def as_normalized(self, size: Size) -> "NormalizeBBox":
def as_denormalized(self, dimension: Size) -> "DenormalizedBBox":
class NormalizedBBox(BBoxBase):
def as_normalized(self, dimension: Size) -> "NormalizedBBox":
return self
def as_denormalized(self, dimension: Size) -> "DenormalizedBBox":
return self.denormalize(dimension)
class DenormalizedBBox(BBoxBase):
def as_normalized(self, dimension: Size) -> "NormalizedBBox":
return self.normalize(dimension)
def as_denormalized(self, dimension: Size) -> "DenormalizedBBox":
return self
With this interface, I’m in a position to comprise the handiest of every worlds – separated kinds for correctness, and a unified interface
for ergonomics.
Divulge: Whilst you happen to would grab so that you just would possibly want to per chance add some shared learn how to the guardian/contemptible class that return an occasion of the corresponding class,
that you just can insist typing.Self
from Python 3.11:
class BBoxBase:
def cross(self, x: recede with the waft, y: recede with the waft) -> typing.Self: ...
class NormalizedBBox(BBoxBase):
...
bbox = NormalizedBBox(...)
# The form of `bbox2` is `NormalizedBBox`, now not factual `BBoxBase`
bbox2 = bbox.cross(1, 2)
Safer mutexes
Mutexes and locks in Rust are most often supplied on the support of an extraordinarily advantageous interface with two benefits:
- Whilst you happen to lock the mutex, you salvage support
a guard
object, which unlocks the mutex automatically when it is destroyed, leveraging the earlier
RAII mechanism:{ let guard = mutex.lock(); // locked right here ... } // automatically unlocked right here
This means that that you just can now not unintentionally put out of your mind to release the mutex. A very identical mechanism is
additionally often frail in C++, even supposing the categoricallock
/release
interface with out a guard object
is additionally available forstd::mutex
, which components that
they’ll peaceful be frail incorrectly. - The info actual by the mutex is saved straight in the mutex (struct). With this create, it is
now not seemingly to salvage entry to the
actual data with out truly locking the mutex. You comprise to lock the mutex first to salvage the
guard, and then you definately salvage entry to the info the insist of the guard itself:let lock = Mutex:: recent(41); // Create a mutex that shops the info inner let guard = lock.lock().unwrap(); // Develop guard *guard += 1; // Regulate the info the insist of the guard
That is in stark incompatibility to the usual mutex APIs existing in mainstream languages (at the side of
Python), the build aside the mutex and the info it protects are separated, and subsequently that you just can
with out difficulty put out of your mind to truly lock the mutex outdated to gaining access to the info:
mutex = Lock()
def thread_fn(data):
# Develop mutex. There could be now not any hyperlink to the real variable.
mutex.compose()
data.append(1)
mutex.free up()
data = []
t = Thread(aim=thread_fn, args=(data,))
t.commence()
# Right here we can salvage entry to the info with out locking the mutex.
data.append(2) # Oops
While we can now not salvage the particular same benefits in Python as we salvage in Rust, now not all is misplaced. Python locks put into effect the
context manager interface, which components that that you just can insist them in a with
block to ensure they are automatically
unlocked on the discontinue of the scope. And with a minute of little bit of effort, we can recede even extra:
import contextlib
from threading import Lock
from typing import ContextManager, Generic, TypeVar
T = TypeVar("T")
# Accomplish the Mutex generic over the value it shops.
# In this arrangement we can salvage upright typing from the `lock` arrangement.
class Mutex(Generic[T]):
# Store the real value within the mutex
def __init__(self, value: T):
# Identify it with two underscores to personal it a minute more difficult to unintentionally
# salvage entry to the value from the skin.
self.__value = value
self.__lock = Lock()
# Provide a context manager `lock` arrangement, which locks the mutex,
# offers the real value, and then unlocks the mutex when the
# context manager ends.
@contextlib.contextmanager
def lock(self) -> ContextManager[T]:
self.__lock.compose()
strive:
yield self.__value
finally:
self.__lock.free up()
# Create a mutex wrapping the info
mutex = Mutex([])
# Lock the mutex for the scope of the `with` block
with mutex.lock() as value:
# value is typed as `checklist` right here
value.append(1)
With this create, that you just can only salvage salvage entry to to the real data after
you in truth lock the mutex. Clearly, that is peaceful Python, so that you just
can peaceful ruin the invariants – e.g. by storing one other pointer to the real data exterior of the mutex.