Skip to content

Why is everything inout and mutating? #234

@VaslD

Description

@VaslD

Is there a sound reason for requiring visit methods inout or mutating? The keyword is so powerful that I cannot implement a simple renderer/themer in a stateless/singleton class because everything everywhere must be mutable.

For something really simple: (I'm actually writing a themer returning NSAttributedString but this simple print example is enough to illustrate my point.)

public final class PrintMarkdown: MarkupWalker {
    public static let shared = PrintMarkdown()

    public func defaultVisit(_ markup: any Markup) {
        print(type(of: markup))
        for child in markup.children {
            child.accept(&self)
                         ^
                         error: cannot pass immutable value as inout argument: 'self' is immutable
        }
    }
}

Because it is a class, I also cannot mark the implementation mutating unlike a struct:

public final class PrintMarkdown: MarkupWalker {
    static let shared = PrintMarkdown()

    public mutating func defaultVisit(_ markup: any Markup) {
           ^
           error: 'mutating' is not valid on instance methods in classes
        print(type(of: markup))
        for child in markup.children {
            child.accept(&self)
        }
    }
}

I understand that structs maybe the go-to choice but a class is useful for overriding and other benefits. A foundation-level parser should not force all implementations to use structs.


Even if I implement a struct, everyone has to var it (no static let and so on). This feels paradoxical because every visit method is required to return a result; meaning the protocol design does not require the visitor/walker to accumulate states between visits. In other words, some implementation like this is entirely valid and to-be-expected:

public struct StringBuilder: MarkupVisitor {
    public mutating func defaultVisit(_ markup: any Markup) -> String {
        var result = String()
        for child in markup.children {
            result += child.accept(&self)
        }
        return result
    }
}

There is nothing mutating except forced keywords. And in protocol-oriented programming, any protocol should define the least required behavior, not implementation details (e.g. stateful).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions