Previously we allowed any msgspec-compatible value to be used as a
default value in a struct. When initialized, the default value would be
deepcopied (with some optimizations for common types) to ensure mutable
state wasn't shared. This was nice and readable (IMO), and let us avoid
implementing `default_factory` support. However, it also had a few
problems:
- Some custom types used as default values are effectively immutable
(e.g. UUIDs, ipaddress, ...). These shouldn't need to be deepcopied,
but there was no way to tell msgspec that.
- Deepcopying is expensive. We had optimizations for common cases (empty
mutable collections like `[]`, `{}`, ...), but the general case had a
large performance cost.
- This only supports "static" default values. Sometimes a user may want
to autogenerate a UUID for a field if one isn't provided, which isn't
possible with the current system.
All of these problems can be solved by dropping the current deepcopying
behavior and adding support for a configurable `default_factory` on
Struct fields. This commit only does the first half.
The new behavior has the following rules:
- Common empty mutable collections (`[]`, `{}`, `set()`, and
`bytearray()`) may be used directly as default values (as a shorthand
for `field(default_factory=list)`. This is purely syntactic sugar,
behind the scenes these are converted to `default_factory`.
- Using common *nonempty* mutable collections (list, dict, set, and
bytearray) as a default value is now an error. We can't check for all
mutable types, so we only try to provide error messages for common
mistakes. To handle these use cases the user should use a
`default_factory`, or switch to an immutable type.
- Using `frozen` struct instances as default values is allowed.
- Using non-frozen struct instances as default values is now an error.
To handle these use cases the user should use a `default_factory`, or
set `frozen=True`.
- Every other type used as a default value is used directly (meaning we
assume they're immutable values).
An added benefit of these changes is that `Struct.__init__` with default
values now has less overhead (although it was already fast).