1
final class TestVC: UIViewController {
    var usersFooter: Footer!
    var groupsFooter: Footer!

    override func viewDidLoad() {
        super.viewDidLoad()

        bind(footer: &usersFooter)
    }

    func bind(footer: inout Footer) {

        footer = Footer(style: .autoFooter, height: 57) {
            // doing something
        }
    }
}

Thats what Footer is:

final class Footer: RefreshView {
    private var loader: MDCActivityIndicator!

    override public init(style: Style, height: CGFloat, action: @escaping () -> Void) {
        // initializing and doing something with loader

        super.init(style: style, height: height, action: action)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

I get this:

Cannot pass immutable value of type 'Footer' as inout argument

How to pass TestVC instance's footers in it's function and be able to initialize them? Why is footer that immutable (Declared as var)?

Zaporozhchenko Oleksandr
  • 4,660
  • 3
  • 26
  • 48

2 Answers2

2

This occurs because

var someVar: Footer!

does not define a variable of type Footer but a variable of type Optional<Footer> that is implicitly unwrapped. The code:

var someFooter: Footer! 

bind(footer: &someFooter) 

is logically equivalent to

var someFooter: Footer? 

guard let tempFooter = someFooter? else { fatalError() }
bind(footer: &tempFooter) 

As you can see, tempFooter is a let, so it can't be passed as an inout variable and even if it could, the result would be thrown away.

You can fix this in one of three ways:

  • make the parameter to bind an optional, e.g. func bind(footer: inout Footer?) or use Martin's syntax to make it implicitly optional.

  • Force the unwrap yourself:

    var unwrapped: Footer = someFooter
    bind(footer: unwrapped)
    someFooter = unwrapped
    
  • redesign the API. It seems the first thing you do in the bind function is overwrite the old footer with a newly initialised footer. So don't use an inout parameter, return the value you want i.e.

    func bind() -> Footer
    {
        var theFooter = Footer(...) { ... }
        // do stuff
    
        return theFooter
    }
    
    someFooter = bind()
    

I think the last option is the best in this case.

JeremyP
  • 84,577
  • 15
  • 123
  • 161
  • Agreed that `inout` probably isn't the right tool for the job here; though `usersFooter!` can be treated as an l-value (i.e you can say `bind(footer: &usersFooter!)`), so I'd argue that OP's code ought to compile. – Hamish Jan 17 '18 at 11:38
  • User's code doesn't compile because the argument is unwrapped to a `let` before being passed to the function. I'd argue that, in this case, it is correct behaviour (on the part of the compiler). – JeremyP Jan 17 '18 at 11:43
  • @JeremyP Thanks, this logical equivalent wasn't so obvious for me – Zaporozhchenko Oleksandr Jan 17 '18 at 12:41
  • @JeremyP But that's inconsistent with the rest of the language; an explicit force unwrap can be treated as an l-value, and an implicit force unwrap can also be treated as an l-value when calling mutating methods, e.g `extension String { mutating func foo() {} }; var bar: String!; bar.foo()` (and a mutating method has an implicit `inout` self parameter). So I don't see any reason why an implicit force unwrap shouldn't be treated as an l-value when used with `inout` explicitly (I'll file a bug when I get a moment). – Hamish Jan 17 '18 at 13:04
  • @Hamish "an explicit force unwrap can be treated as an l-value" The target of an assignment is handled differently to evaluating an expression and then passing it as an inout parameter. The *semantics* of `inout` are that the result of the expression is copied into the parameter at the beginning of the function and then copied back out at the end of the function. The problem is that the expression yields an immutable value. Hence the error message. – JeremyP Jan 17 '18 at 14:06
  • @JeremyP I'm not quite sure I understand you. Explicit force unwrapping can yield an l-value, e.g `var x: String? = "a"; x! += "b"` & `bind(footer: &usersFooter!)` in OP's code. Why should implicit force unwrapping be any different? – Hamish Jan 17 '18 at 15:02
  • Your example is an **assignment** to an unwrapped variable. Whereas an argument to an inout expression is not. Arguing that it works for one language construct but not another doesn't work. Personally, I would argue that explicit unwrapping of inout variables should also give the same error. – JeremyP Jan 17 '18 at 16:46
  • @JeremyP There's no (explicit) assignment in any of my above examples (well okay, apart from `var x: String? = "a"` ;) ). `x! += "b"` calls the `RangeReplaceableCollection` operator overload `+= (lhs: inout Self, rhs: Other)`. `x!` is passed as `inout`. It's worth noting btw that the ability to use force unwrapped values as l-values is pretty important when it comes to things like optional chaining. – Hamish Jan 17 '18 at 17:09
  • The `+=` operator is an assignment operator. Swift treats it as a special case. Explicit unwrapping in inout variables are also treated as special cases. The implicit unwrapping is not allowed and that's the end of it until somebody gets their proposal to change it accepted on Swift evolution. – JeremyP Jan 17 '18 at 17:21
  • @JeremyP The special case here is that `x!` can be treated as an l-value (user-defined operators can't do this), and everything else falls from that (being able to use it with `inout`, etc.). Try printing out the AST for the above example with `x! += "b"` and you'll see `x!` being represented as `force_value_expr type='@lvalue String'`. – Hamish Jan 17 '18 at 17:35
  • I don't believe however that `+=` is in any way recognised as a special operator by the compiler (it's not listed in [*KnownIdentifiers.def*](https://github.com/apple/swift/blob/master/include/swift/AST/KnownIdentifiers.def) nor [*KnownDecls.def*](https://github.com/apple/swift/blob/master/include/swift/AST/KnownDecls.def). FWIW here's a (pretty old) related bug report on the subject: https://bugs.swift.org/browse/SR-853. – Hamish Jan 17 '18 at 17:35
0

Write bind method like this. It will resolve your error.

func bind(footer: inout Footer!) {

    footer = Footer(style: .autoFooter, height: 57) {
        // doing something
    }
}

It seems like inout thinks Footer & Footer! are different. Either update method as above or change declaration as given below.

var userFooter: Footer

I don't know the exact reason but the error which we are getting is confusing.

Martin
  • 846
  • 1
  • 9
  • 23