This doesn't work correctly:
func foo<T>(_ v: T) {
if let val = v as? AnyObject {
print("reference type passed")
} else {
print("value type passed")
}
}
foo(42)
How do I check if reference or value type is passed?
This doesn't work correctly:
func foo<T>(_ v: T) {
if let val = v as? AnyObject {
print("reference type passed")
} else {
print("value type passed")
}
}
foo(42)
How do I check if reference or value type is passed?
Swift can box value types to AnyObject
, so a cast to AnyObject
will always succeed.
See How to distinguish between value type and reference type?
Checking if an object is a value type vs a reference type isn't as useful as it seems, as there are values with reference semantics (e.g. a struct storing a reference to a shared mutable object) and referenced objects with value semantics (e.g. an immutable class instance with only other values as fields).
What you're probably looking for is the ability to tell if something has value semantics or reference semantics (and treat it differently based on that). The rules there are pretty nuanced/complicated (e.g. is a thread-safe, lazy loaded value a value or a reference? Depends on what you're looking for.)
If you just need to know whether a type is a class/actor
or struct/enum/tuple
, regardless of the semantics of the type, you could use this:
func foo<T>(_ v: T) {
if T.self is AnyClass {
print("reference type passed")
} else {
print("value type passed")
}
}
Looks hopeful, but doesn't work How to distinguish between value type and reference type? - #3 by AlexanderM
If it's not about the semantics, and just about specifically whether something is a class
, then it would work. While a closure has reference semantics, it isn't a class. It might make more sense for the original author to define two separate functions:
func foo(_ v: some Any) {
print("value type passed")
}
func foo(_ v: some AnyObject) {
print("reference type passed")
}
I don't think closures are reference types (I know swift book claims they are but it is wrong IMHO). I can create a value type (holding a reference) that would have reference semantics - doesn't make that value type reference type, ditto I believe is the case with closures.
Consider this. Here are normal value semantics with a struct:
struct MyStruct {
var s = "original value"
mutating func mutate() {
s = "mutated value"
}
}
func f(_ arg: MyStruct) {
// Swift forced you to make a copy explicitly, otherwise you're not
// allowed to call the mutating method.
var localCopy = arg
print("localCopy before mutating: \(arg)") // => MyStruct(s: "original value")
localCopy.mutate()
print("localCopy after mutating: \(arg)")// => MyStruct(s: "mutated value")
}
let myStruct = MyStruct()
print("myStruct before f: \(myStruct)") // => MyStruct(s: "original value")
f(myStruct)
print("myStruct before f: \(myStruct)") // => MyStruct(s: "original value")
A struct lets you mutate a local copy without effecting the original. This is because copying a struct copies its value, and the local copy is completely independent.
Since a closure is a reference type, copying it entails copying a reference to the same referenced object, just like with an instance of a class. This has observable implications on the meaning of programs:
func createState<T>(intialValue: T) -> (T?) -> T {
var state = intialValue
@discardableResult
func statefulClosure(newValue: T?) -> T {
if let newValue { // Behave like a setter
state = newValue
}
return state
}
return statefulClosure
}
func f(_ arg: (String?) -> String) {
// The copy is now redundant here:
var localCopy = arg // warning: variable 'localCopy' was never mutated; consider changing to 'let' constant
print("localCopy before mutating: \(localCopy(nil))") // => "original value"
_ = localCopy("mutated value") // This mutates the shared state
print("localCopy after mutating: \(localCopy(nil))")// => "mutated value"
}
let myClosure = createState(intialValue: "original value")
print("myClosure before f: \(myClosure(nil))") // => "original value"
f(myClosure)
print("myClosure after f: \(myClosure(nil))") // => "mutated value"
Hey Aggie,
Your comment mentions class/actor
and struct/enum/tuple
specifically, but the print statements still use the more general terms "reference type" and "value type", which I must reiterate, is simply incorrect here.
import Foundation
func foo(_ v: some Any) {
print("value type passed")
}
func foo(_ v: some AnyObject) {
print("reference type passed")
}
let aClosure: () -> Int = { 123 }
foo(aClosure) // "value type passed" ... this is just wrong.
Oh, also:
class C {} // _definitely_ a class
let any2: Any = C() // the static type is any, even though the runtime type is definitely a class
foo(any2) // "value type passed"
If the user is supposed to pass Any
, you could use _openExistential
.
func foo(_ v: Any) {
_openExistential(v, do: _foo)
}
func _foo<T>(_ v: T) {
if T.self is AnyClass {
// T is specifically a class type. It may have value or reference semantics.
print("class type")
} else {
// T is specifically NOT a class, but may be any other type.
print("non-class type")
}
}
If you really want to drill down deep, you could use a reflection library.
import IntrospectionKit // https://github.com/gor-gyolchanyan-swift/introspection-kit
func foo(_ v: Any) {
_openExistential(v, do: _foo)
}
func _foo<T>(_ v: T) {
switch TypeIntrospection(rawValue: T.self).kind {
case .classFromC, .classFromObjectiveC, .classFromSwift:
print("Class type passed")
case .enumeration:
print("Enum type passed")
case .function:
print("Closure passed")
case .generic:
print("Generic type passed")
case .opaque:
print("Opaque type passed")
case .optional:
print("Optional type passed")
case .structure:
print("Structure passed")
case .tuple:
print("Tuple passed")
case .typeForGeneric:
print("Type for generic")
case .typeForSpecific:
print("Type for specific")
}
}
foo(5)
// Structure passed
foo(NSString(string: "Hello"))
// Class type passed
let baz: Any = [1, 2, 3]
foo(baz)
// Structure passed
But if the original poster wants you to pass a reference or value type, that's an API contract that needs to be explicitly made with the user, since a struct
could still be a reference type and vice-versa, like you said.
// Is a struct, but with reference semantics
struct Decoy {
var reference: NSMutableString
}
// Is a class, but with value semantics
class Immutable {
let n: Int
let y: Double
}
Also, even though closures are reference types, you could argue they have value semantics since they're immutable. What would copying a closure do?
Closures are conceptually this struct:
struct Closure {
var functionPointer: UnsafeRawPointer
var closureContext: AnyObject?
}
That makes closures value types.
True, value types can have reference semantics (and vice versa).
Sure, but couldn't the same be said about instances of (non-class-bound) protocol, whose existential containers are pretty much the same thing? (typically, a pointer to an object, and a pointer to a value witness table full of function pointers).
IMO you're circling back to my original idea: The distinction between "value type" and "reference type" is not actually all that useful. People tend to stumble into it first, and it feels like it's a proxy for the more useful concept ("value semantics" vs "reference semantics"), but we've already shown, it's not a 1:1 relationship.
I wonder if there could be some way for the compiler to statically know whether a type has value semantics or reference semantics.
// swift could add something like this:
@_marker public protocol ValueSemantics { }
Then, a type could only conform to ValueSemantics
if it was statically known to have only ValueSemantics
conforming properties or associated values and it was a value type.
All of the UnsafePointer types would not conform to ValueSemantics
.
A type could declare that it has value semantics by extending itself with @unchecked ValueSemantics
.
Instances of (non-class-bound) protocol are value types as well (I wouldn't use instances word though, to me that word implies reference types).
It's impossible to determine if the thing has value or reference semantics (in general) - for example the type might deliberately change its behaviour (just to be nasty). The question of the topic is much easier, determine if something is reference type or not. Unless I am seriously mistaken the only reference types in Swift are class
and actor
(again, despite that swift book labels closures reference types, IMHO they are not).
class ClassThing {
}
struct StructThing {
}
let u = ClassThing () // `u` is an instance of `ClassThing`
let v = StructThing () // `v' is an instance of `StructThing`
What word would you use instead of instance for v
?
same as for:
let u = 42
let v = 3.1415
which I personally won't call "instances of int/float". Just int / float values. So - "StructThing value".