afero icon indicating copy to clipboard operation
afero copied to clipboard

New*Fs functions should return the concrete type

Open bep opened this issue 8 years ago • 12 comments

Having to do:

 NewBasePathFs(fs, baseDir).(*BasePathFs)

Doesn't make much sense. I don't see the problem going the other way around; but as this is a file system with a very specific contract, I want to know what it is.

bep avatar Mar 21 '16 23:03 bep

you mean like this?

@@ -22,7 +22,7 @@ type BasePathFs struct {
        path   string
 }

-func NewBasePathFs(source Fs, path string) Fs {
+func NewBasePathFs(source Fs, path string) *BasePathFs {
        return &BasePathFs{source: source, path: path}
 }

vetinari avatar Apr 18 '16 16:04 vetinari

Yes. Not sure why it is a pointer, but that is another debate.

bep avatar Apr 18 '16 17:04 bep

This was introduced in a commit https://github.com/spf13/afero/commit/fe8e8953368bff0a84babecf4e2a6c965c0453c8 where @spf13 added a lot of New*Fs() function, and most of them return Fs. I also think the concrete type would be more useful in this case, as people can just assign it to a var like var fs afero.Fs = afero.NewBasePathFs(). I believe the pointer here is not needed, but only for mutable things like MemMapFs. Probably every of the New*Fs should return the concrete type.

mbertschler avatar Apr 18 '16 17:04 mbertschler

I saw a discussion on gopher-nuts about this, and I believe one of the Go core devs argued that functions/methods should take interfaces as arguments when possible, but return concrete types. I agree.

bep avatar Apr 18 '16 18:04 bep

Makes sense, because this is the most flexible way. If the argument is the interface, you can pass the interface or concrete value, and if the return is the concrete type you can assign it to an interface or concrete variable, all without any assertions.

mbertschler avatar Apr 18 '16 19:04 mbertschler

Is there a reason why this is not of high priority? Seems like an easy fix, and high impact as it changes the public signature.

xiegeo avatar Mar 05 '17 01:03 xiegeo

I disagree with this issue principally because of the rules of generality in Effective Go. Read it since I won't repeat the argument.

BasePathFs and MemMapFs are the only Fs implementations with an extra method: RealPath and List, respectively. BasePathFs.RealPath should not be exported in the first place -- that's what Stat is for. MemMapFs.List looks like a debugging feature that most people are likely not using; I'd suggest unexporting or removing List.

Since all Fs implementations should be exporting the same methods, there's no need to export a concrete type. Everything we need is in the interface.

moorereason avatar Apr 25 '17 15:04 moorereason

@moorereason to get some effect out of what you're saying, you need to un-export the concrete types, which would be painful in the wild.

My main argument for returning the concrete type here is the case of afero.BasePathFs. This is security related; I really want to know that it is this special kind of restricted FS. Currently I could do a type assertion to check, which i could not do if the type was unexported, and then you might as well just return it.

bep avatar Apr 25 '17 15:04 bep

I'm not saying that BasePathFs itself should not be exported. It is and should be. I only mean that BasePathFs.RealPath should be unexported. RealPath adds a method that's already wrapped by Stat.

If you are concerned about the type of the underlying Fs implementation, a type assertion is the way to go.

moorereason avatar Apr 25 '17 16:04 moorereason

I only mean that BasePathFs.RealPath should be unexported.

Sure, but that is a topic for another thread.

The "effective Go" link you provide seems to be old. I have seen more recent comments by core Go people that evangelise "interface in, type out". There are, of course, exceptions to this rule, most often when you don't care/know about the type. With file systems, you often care about what it really is, and the API gets much clearer when you return the concrete type: If you need to use it as an interface, just use it as is, if you need the concrete type, no need to to do magic type casting: It is obvious in type func signature what you get.

bep avatar Apr 25 '17 18:04 bep

An added note to the above: I have a long background with Java, and I kind of hate when people talk about idomatic Go -- but when it comes to interfaces, you will have to think differently for it to be really effective.

bep avatar Apr 25 '17 19:04 bep

imo filesystem abstractions may provide more than just what the Fs interface has to offer. In my case rollback mechanics of filesystem modifications.

You can basically put implementations into functions requiring the interface but getting the implementations back from an interface is imo a giant hassle.

jxsl13 avatar Mar 01 '22 22:03 jxsl13