Por qué la pregunta "How to check if a file exists in Go?" se volvió viral
\n\nSi alguna vez ha escrito un programa en Go que trabaja con archivos, seguramente se ha topado con esta pregunta. En Stack Overflow ha acumulado cientos de miles de visitas, y debajo, decenas de comentarios con acalorados debates. Parecería algo simple: en Python existe os.path.exists(), en PHP — file_exists(), ¿y en Go?
El problema es que Go no tiene una función incorporada FileExists(). Y esto no es casualidad. Los desarrolladores del lenguaje evitaron intencionalmente este enfoque porque verificar la existencia de un archivo es una condición de carrera (race condition) en estado puro. Mientras usted verifica, el archivo puede ser eliminado o creado por otra goroutine. Por lo tanto, en Go se acostumbra simplemente intentar abrir el archivo y manejar el error.
Pero, ¿cómo hacerlo correctamente? ¿Cómo no perder rendimiento y no toparse con errores ocultos? Hoy analizaremos la verificación de existencia de archivos en Go de principio a fin: desde ejemplos básicos hasta soluciones para producción.
\n\nPrimer enfoque: os.Stat y os.IsNotExist (clásico de Stack Overflow)
\n\nLa respuesta más popular en Stack Overflow se ve así:
\n\npackage main\n\nimport (\n "fmt"\n "os"\n)\n\nfunc fileExists(filename string) bool {\n info, err := os.Stat(filename)\n if os.IsNotExist(err) {\n return false\n }\n return err == nil && !info.IsDir()\n}\n\nfunc main() {\n if fileExists("example.txt") {\n fmt.Println("El archivo existe")\n } else {\n fmt.Println("Archivo no encontrado")\n }\n}\n\n\nEste código usa os.Stat(), que devuelve información sobre el archivo. Si el archivo no existe, la función devuelve un error, y lo verificamos mediante os.IsNotExist(). Adicionalmente, verificamos que no sea un directorio — ya que os.Stat() también funciona en carpetas.
Ventajas: simplicidad, legibilidad, mínimo de dependencias.
\n\nDesventajas: potencial condición de carrera entre la verificación y la apertura del archivo. Si verifica la existencia y luego abre el archivo, entre estas operaciones el archivo puede desaparecer. En Go se acostumbra intentar abrir el archivo directamente y manejar el error.
\n\nRendimiento de os.Stat
\n\nLa llamada a os.Stat() es una llamada al sistema. En Linux es stat() o lstat(). Es rápida, pero no instantánea. En promedio, una llamada toma de 1 a 5 microsegundos en un SSD. Si verifica cientos de miles de archivos, esto puede convertirse en un cuello de botella.
Optimización: si necesita verificar la existencia de múltiples archivos, considere almacenar en caché los resultados o usar filepath.Glob() con un patrón.
Segundo enfoque: apertura del archivo y manejo de errores (Go Way)
\n\nUna forma más idiomática es intentar abrir el archivo directamente. Esto resuelve el problema de la condición de carrera:
\n\npackage main\n\nimport (\n "fmt"\n "os"\n)\n\nfunc openFile(filename string) (bool, error) {\n file, err := os.Open(filename)\n if err != nil {\n if os.IsNotExist(err) {\n return false, nil\n }\n return false, err // Otro error: sin permisos, enlace roto, etc.\n }\n defer file.Close()\n return true, nil\n}\n\nfunc main() {\n exists, err := openFile("example.txt")\n if err != nil {\n fmt.Printf("Error: %v\\n", err)\n return\n }\n if exists {\n fmt.Println("El archivo existe y está accesible")\n }\n}\n\n\nEste código abre el archivo inmediatamente. Si surge un error, verificamos si es precisamente el error de "archivo no encontrado". Cualquier otro error (por ejemplo, permisos insuficientes) lo devolvemos hacia arriba — esto brinda más control.
\n\nVentajas: atomicidad de la operación, manejo correcto de errores, ausencia de condición de carrera.
\n\nDesventajas: abrimos el archivo incluso si solo necesitamos verificar su existencia. Para archivos grandes, esto puede ser costoso