Is it ok that I cannot instantiate?

Take a look at the following code:

class A {
  var a = 0
  class B {
     var b: Int { a }
  }
}

It seems reasonable, but compiler thinks the other way: error: instance member 'a' of type 'A' cannot be used on instance of nested type 'A.B'

This assumption would be correct if this code had been used like this: A.B(), since it depends on value that doesn't exist.
But isn't use like this - A().B() - perfectly sound?

Don't you think that this behaviour should be changed?

Unless I'm getting something completely wrong: Absolutely not.
There is no connection between an instance of the inner type to the outer type, and A() is not a namespace, but a plain object.
What is your use case for this wish?

1 Like

Answering the topmost question — "Is it ok that I cannot instantiate?" — yes, because that's how Swift's type system currently works. Nested types in Swift as of now serve only namespasing purposes; beyond that, they are not "linked" to the containing type and aren't aware of any of its properties or methods.

Simply adding support for such behaviour is not exactly trivial, since you would need to design adequate ways to specify how the nested type captures the instance of the enclosing type — much like closures work — otherwise, when not used carefully, this can cause reference cycles.

Regardless, this would just be syntactic sugar. It is totally possible to achieve what you want by rewriting the code in a way similar to:

class A {
  var a = 0

  class B {

    private init(enclosing: A) {
        self.enclosing = enclosing
    }

    private let enclosing: A
    var b: Int { enclosing.a }
  }

    func makeB() -> B {
        return B(enclosing: self)
    }
}

— as you can see, it doesn't dramatically complicate the code, while also ensuring that you will be able to specify strong/weak/unowned references in an already familiar way.

3 Likes

I can talk about this from the Java perspective, which supports inner classes.

Their inner classes default to non-static, that is, the inner class can't be talked about separate from the instance it belong to. Each instance of the non-static inner class has an implicit reference to the parent instance.

Java Example
class Outer {
	int i = 123;
	
	void printInfo() {
		System.out.println("Outer.printInfo()");
		System.out.println("\tthis: " + this);
		System.out.println("\ti: " + i);
	}
	
	class Inner {
		void printInfo() {
			System.out.println("\nInner.printInfo()");
			System.out.println("\tthis: " + this);
			System.out.println("\tParent: " + Outer.this); // Has access to the parent object
			System.out.println("\tParent's i: " + i); // Has implicit access to parent object fields/methods
		}
	}
	
	static class StaticInner {
		void printInfo() {
			System.out.println("\nStaticInner.printInfo()");
			System.out.println("\tthis: " + this);
//			System.out.println("\tParent: " + Outer.this); // No access to the parent object
//			System.out.println("\tParent's i: " + i); // No access to parent object fields/methods
		}
	}
}

class Untitled {
	public static void main(String[] args) {
		var outer = new Outer();
		outer.printInfo();
		
		var inner = outer.new Inner(); // Notice how this construction is tied to `outer`
		inner.printInfo();
		
		var staticInner = new Outer.StaticInner(); // Notce how this construction stands alone
		staticInner.printInfo();
	}
}

Output:

Outer.printInfo()
	this: Outer@6d1e7682
	i: 123

Inner.printInfo()
	this: Outer$Inner@68fb2c38
	Parent: Outer@6d1e7682
	Parent's i: 123

StaticInner.printInfo()
	this: Outer$StaticInner@2eafffde|

I think there's few things to note:

  1. Swift needs to be weary of reference cycles. An implicit reference to the parent instance's variable (like the one you wrote) means there's a strong reference. If it were weak, a would be optional.

    This means that parent -> child references would have to be weak, which is backwards to the usual Swift convention

  2. I think there's a good argument to be made that "non-static by default" was the wrong choice. Inner classes should have been static by default, and opt-in to being instance-bound, if such a feature exists, at all.

  3. Non-static inner classes are usually suspicious. They let you break down your large classes, but only on a technicality. The resulting class cluster is heavily coupled (by all the implicit references to parent fields/methods), in a way that doesn't make them very useful as a method of decomposition. I have found it useful as an intermediate refactoring step, on the trip to standalone classes with proper interfaces between each other.

  4. In Java, they even have access to the parent instance's private state. In a sense, this makes them very similar to Swift's extension feature (when both the extension and target type are in the same module), or like a limited friend in C++.

  5. If you want your outer and inner class to have this kind of relationship, you can just pass the parent as an argument to the child's initializer. Sure, it's a bit of extra code, but I think that the syntactic salt is worth it, in order to discourage people from building large, coupled balls of mud.

3 Likes

Perhaps you meant:

class A {
  static var a = 0
  class B {
     var b: Int { a }
  }
}

Which does work. This goes to the idea that A just acts as a namespace. If you want b to vary on the different instances of A, @wtedst 's solution is good.

Terms of Service

Privacy Policy

Cookie Policy