AnyValue, Immutable protocols, deprecate ": class" protocol constraint

I’d like to propose introducing the AnyValue protocol as a counterpart to AnyObject. I.e., a protocol to which all value types including structs, enums, and primitives implicitly conform. I’d also like to propose deprecating the protocol Foo: class syntax for constraining protocols in favor of Foo: AnyObject or Foo: AnyValue. Foo: AnyObject would behave identically to Foo: class, while Foo: AnyValue wouldn’t change behavior, but it would prevent objects from implementing the protocol.

I’d also like to propose the addition of an Immutable protocol that would have no requirements, but would cause the compiler to enforce immutability. E.g. on a class or struct, all properties must be let, and a protocol constrained Foo: Immutable, { get set } properties and mutating functions would cause compiler errors.

What do you think?

For me, enforcing let properties doesn’t make much sense for structs:
If a struct shouldn’t be changed, just declare the instance with let.
Classes are different, though - I wonder if there have been thoughts to restrict the mutability of let class instances more when the concept of “mutable” for structs was added…

1 Like

I was pretty sure that has already been decided (I’m really sure it was discussed ;-).
There’s no real need to enforce that some protocol can only be implemented by structs, but imho it wouldn’t hurt either. I’m not such a big fan of the naming, though:
I’d name the restriction like the keyword uppercased (instead of class -> AnyObject, it would be class -> Class, struct -> Struct, enum -> Enum)

There actually might be a subtle difference between : class and : AnyObject as @codafi pointed out in this stackoverflow answer: https://stackoverflow.com/a/32895975/756039
Not sure if this is still valid or if this has changed in the meantime.

Other than that, should a protocol really dictate the semantics of a type that implements it?

To me it seems at the moment that limiting an implementation to be a class is a workaround for an ownership problem. Other than having to store a reference to an implementing type without taking a strong reference of it (weak) I wouldn’t know when I ever want to restrict a protocol to be class-only.

For what purpose would you need : AnyValue?

: class is already deprecated as per SE-0156 but @Slava_Pestov couldn’t implement it in time or something like this.

There is no real benefit form : Struct or : Enum constraints. The naming behind : AnyObject also implies that in the future there might be more reference types which can conform to protocols and not just the class as we know it.

I raised my hand for the : AnyValue constraint before. Please everyone who’s going to jump into this discussion don’t mess it up and make another huge thread about value semantics because the AnyValue has nothing to do with value semantics as it’s a totally different problem to this constraint.

I’ll hold off on the comments about value semantics for now. Can you remind us what use cases you know of for this constraint that do not implicitly imply value semantics? I don’t recall anyone ever producing a compelling answer to that question.

1 Like

I don’t think there’s a real use case… but if there was a way to enforce “structness” ;-), some useful things might be possible:
Value types which are guaranteed not to contain any class properties would not only be “truly” immutable, but could also be serialized and restored without detailed knowledge about the internal structure.

I see the subtype relationship a little different:

  • SomeClass : (May or may not conform to ValueSemantical) : AnyObject : Any
  • SomeValue : (May or may not conform to ValueSemantical) : AnyValue : Any

Classes can have value semantics as well and if possible I’d have an explicit constraint for that too so that we can guarantee it. Swift’s value types are designed around value semantics but there is no guarantee that they always behave that way, at least no explicit one. You may not always want your structs to have value semantics, but that depends on what you’re designing.

1 Like

This doesn’t even begin to answer the question of how you think AnyValue would be used. What use cases are there? What generic code could be written with it that cannot be written today?

Well I had a use case for that this week which hit me right in the face during an app review. This operator caused the issue because it was not constrained to value types:

public func <- <T>(lhs: T, rhs: (inout T) throws -> Void) rethrows -> T {
  var value = lhs
  try rhs(&value)
  return value
}

It does compile fine, it also does work as expected when running the app with Xcode on your device, but a release archive will crash badly if T as a class type.

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000358
VM Region Info: 0x358 is not in any region.  Bytes before following region: 4295654568
      REGION TYPE                      START - END             [ VSIZE] PRT/MAX SHRMOD  REGION DETAIL
      UNUSED SPACE AT START
--->  
      __TEXT                 00000001000a8000-00000001000ac000 [   16K] r-x/r-x SM=COW  ...pp/projectname]

Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [0]
Triggered by Thread:  0

Obviously this is a compiler bug, but hey with the AnyValue I speak about I would be at least save if I had two separate <- functions where one of those is constrained to AnyObject and the other to AnyValue.

Well to be totally fair I might want a different AnyValue then you do, but that’s okay. The issue is that we probably won’t get this constraint any time soon if we pursue it to implicitly imply value semantics because it isn’t a trivial thing to accomplish.

I’m not opposed to this on principle. I’m only opposed until compelling use cases can be demonstrated. AnyObject carries with it semantics and capabilities that allow you to write interesting code you couldn’t without it. This does not appear to be true of AnyValue despite significant discussion in the past.

The crash you’re seeing with the above code might be a bug. The fact that it works in debug builds makes me think it could be an optimization issue. Have you filed a ticket for it?

1 Like

Offtopic:

I discussed the issue with some other people and the also assumed this could be an issue where the optimizer has been too aggressive. In fact this code does run even if you build it for release directly on your device but crashes when you install the app from an Xcode archive.

To answer your question: not yet I had no time to create a small project which can reproduce the issue, but I probably will in the next few days.


Edit:

@anandabits retrofitting AnyValue at this stage with implicit value semantics would be a huge breaking change no? I don’t think every value type out there does follow value semantics.

I think the idea about structs having reference-semantics overlaps with move-only types (e.g. IIRC, move-only structs would be allowed to have deinitializers, and I think they can mutate even from non-mutating methods, at least conceptually).

Maybe the other side of the story (classes with value semantics) would take time.

I don’t really know enough about this area; I’m going to have to read the ownership manifesto a few more times, but I think there is some overlap between move-only and Copyable types and value/reference semantics.

Indeed, using class as a protocol requirement/conformance was deprecated by a previous proposal, but it’s not implemented yet. I’ve opened an issue in the bug tracker. @jrose , do you have any news?

https://bugs.swift.org/browse/SR-7025

You’ve nicely described what you think would be a good addition to Swift, but haven’t touched on why they’d fit into the language. Could you describe the problems you’re encountering that AnyValue and Immutable are trying to solve?

: class and : AnyObject now parse the same. It would be trivial to emit a warning when we see the former. Would be a good starter bug for someone who wants to learn.

I’ve previously suggested that protocols with mutating requirements should implicitly be struct/enum-only (or alternatively, class implementations should be able to reassign self). If both a class and a struct can conform to a protocol with a mutating method, generic users of that protocol cannot know whether the method will—or even can—provide value semantics for that method.

Value semantics is something that cannot currently be guaranteed by the language at all. It can at best be a documented semantic requirement. I don’t think changes like preventing classes from conforming to protocols with mutating requirements or introducing AnyValue are well motivated by an argument based on value semantics. They may be worthwhile changes but only if they can be motivated by other use cases.

1 Like