Is there no default(T) in Swift?

2020-07-02 08:08发布

问题:

I'm trying to port the Matrix example from Swift book to be generic.

Here's what I got so far:

struct Matrix<T> {
    let rows: Int, columns: Int
    var grid: T[]

    init(rows: Int, columns: Int, repeatedValue: T) {
        self.rows = rows
        self.columns = columns

        grid = Array(count: rows * columns, repeatedValue: repeatedValue)
    }

    func indexIsValidForRow(row: Int, column: Int) -> Bool {
        return row >= 0 && row < rows && column >= 0 && column < columns
    }

    subscript(row: Int, column: Int) -> T {
        get {
            assert(indexIsValidForRow(row, column: column), "Index out of range")
            return grid[(row * columns) + column]
        }
        set {
            assert(indexIsValidForRow(row, column: column), "Index out of range")
            grid[(row * columns) + column] = newValue
        }
    }
}

Note that I had to pass repeatedValue: T to the constructor.

In C#, I would have just used default(T) which would be 0 for numbers, false for booleans and null for reference types. I understand that Swift doesn't allow nil on non-optional types but I'm still curious if passing an explicit parameter is the only way, or if I there is some equivalent of default(T) there.

回答1:

There isn't. Swift forces you to specify the default value, just like then you handle variables and fields. The only case where Swift has a concept of default value is for optional types, where it's nil (Optional.None).



回答2:

An iffy 'YES'. You can use protocol constraints to specify the requirement that your generic class or function will only work with types that implement the default init function (parameter-less). The ramifications of this will most likely be bad (it doesn't work the way you think it does), but it is the closest thing to what you were asking for, probably closer than the 'NO' answer.

For me I found this personally to be helpful during development of a new generic class, and then eventually I remove the constraint and fix the remaining issues. Requiring only types that can take on a default value will limit the usefulness of your generic data type.

public protocol Defaultable
{
  init()
}

struct Matrix<Type: Defaultable>
{
  let rows: Int
  let columns: Int
  var grid: [Type]

  init(rows: Int, columns: Int)
  {
    self.rows = rows
    self.columns = columns

    grid = Array(count: rows * columns, repeatedValue: Type() )
  }
}


回答3:

There is a way to get the equivalent of default(T) in swift, but it's not free and it has an associated hazard:

public func defaultValue<T>() -> T {
    let ptr = UnsafeMutablePointer<T>.alloc(1)
    let retval = ptr.memory
    ptr.dealloc(1)
    return retval;
}

Now this is clearly a hack because we don't know if alloc() initializes to something knowable. Is it all 0's? Stuff left over in the heap? Who knows? Furthermore, what it is today could be something different tomorrow.

In fact, using the return value for anything other than a placeholder is dangerous. Let's say that you have code like this:

public class Foo { /* implementation */
public struct Bar { public var x:Foo }
var t = defaultValue<Bar>();
t = someFactoryThatReturnsBar(); // here's our problem

At the problem line, Swift thinks that t has been initialized because that's what Swift's semantics say: you cannot have a variable of a value type that is uninitialized. Except that it is because default<T> breaks those semantics. When you do the assignment, Swift emits a call into the value witness table to destroy the existing type. This will include code that will call release on the field x, because Swift semantics say that instances of objects are never nil. And then you get a runtime crash.

However, I had cause to interoperate with Swift from another language and I had to pass in an optional type. Unfortunately, Swift doesn't provide me with a way to construct an optional at runtime because of reasons (at least I haven't found a way), and I can't easily mock one because optionals are implemented in terms of a generic enum and enums use a poorly documented 5 strategy implementation to pack the payload of an enum.

I worked around this by passing a tuple that I'm going to call a Medusa tuple just for grins: (value: T, present: Bool) which has the contract that if present is true, then value is guaranteed to be valid, invalid otherwise. I can use this safely now to interop:

public func toOptional<T>(optTuple: (value:T, present:Bool)) -> T? 
{
    if optTuple.present { return optTuple.value }
    else { return nil }
}

public func fromOptional<T>(opt: T?) -> (T, Bool)
{
    if opt != nil { return (opt!, true) }
    else {
        return (defaultValue(), false)
    }
}

In this way, my calling code passes in a tuple instead of an optional and the receiving code and turn it into an optional (and the reverse).