code: func (gs *impl) Reader(c context.Context, path string, gen int64) (Reader, error) {
  	logging.Infof(c, "gs: Reader(path=%q, gen=%d)", path, gen)
  	if err := gs.init(); err != nil {
  		return nil, err
  	}
  
  	// Fetch the object metadata, including its size and the generation number
  	// (which is useful when 'gen' is <= 0).
  	call := gs.srv.Objects.Get(SplitPath(path)).Context(c)
  	if gen > 0 {
  		call.Generation(gen)
  	}
  	var obj *storage.Object
  	err := withRetry(c, func() (err error) { obj, err = call.Do(); return })
  	if err != nil {
  		return nil, errors.Annotate(err, "failed to grab the object size and generation").Err()
  	}
  
  	// Carry on reading from the resolved generation. That way we are not
  	// concerned with concurrent changes that may be happening to the file while
  	// we are reading it.
  	return &readerImpl{c, gs, path, int64(obj.Size), obj.Generation}, nil
  }
docs: // Reader returns an io.ReaderAt implementation to read contents of a file at
  // a specific generation (if 'gen' is positive) or at the current live
  // generation (if 'gen' is zero or negative).