El uso de un lenguaje más estricto no solo mueve las publicaciones de objetivos desde la correcta implementación hasta la correcta especificación. Es difícil hacer algo que esté muy mal pero sea lógicamente consistente; Por eso los compiladores detectan tantos errores.
La aritmética de puntero, como se formula normalmente, no es correcta porque el sistema de tipos no significa realmente lo que se supone que significa. Puede evitar este problema por completo trabajando en un lenguaje recolectado de basura (el enfoque normal que hace que también pague por la abstracción). O puede ser mucho más específico acerca de los tipos de punteros que está utilizando, de modo que el compilador pueda rechazar cualquier cosa que sea inconsistente o simplemente no pueda probarse como está escrito. Este es el enfoque de algunos idiomas como Rust.
Los tipos construidos son equivalentes a las pruebas, por lo que si escribe un sistema de tipos que lo olvida, entonces todo tipo de cosas salen mal. Supongamos por un tiempo que cuando declaramos un tipo, en realidad queremos decir que estamos afirmando la verdad sobre lo que está en la variable.
- int * x; // Una afirmación falsa. x existe y no apunta a un int
- int * y = z; // Solo es cierto si se demuestra que z apunta a un int
- * (x + 3) = 5; // Solo es cierto si (x + 3) apunta a un int en la misma matriz que x
- int c = a / b; // Solo es cierto si b es distinto de cero, como: "nonzero int b = ...;"
- nulable int * z = NULL; // nullable int * no es lo mismo que int *
- int d = * z; // Una afirmación falsa, porque z es anulable
- if (z! = NULL) {int * e = z; } // Ok porque z no es nulo
- libre (y); int w = * y; // Afirmación falsa, porque y ya no existe en w
En este mundo, los punteros no pueden ser nulos. Las desreferenciaciones de puntero nulo no existen y no es necesario comprobar la nulidad de los punteros en ninguna parte. En cambio, un "nullable int *" es un tipo diferente que puede tener su valor extraído en nulo o en un puntero. Esto significa que en el punto donde comienza la suposición no nula, puede registrar su excepción o descender por una rama nula.
En este mundo, los errores fuera de los límites tampoco existen. Si el compilador no puede probar que está dentro de los límites, intente reescribir para que el compilador pueda probarlo. Si no puede, entonces tendrá que poner una Asunción manualmente en ese lugar; el compilador puede encontrar una contradicción más adelante.
Además, si no puede tener un puntero que no esté inicializado, no tendrá punteros para la memoria no inicializada. Si tiene un puntero para liberar memoria, entonces debe ser rechazado por el compilador. En Rust, hay diferentes tipos de punteros para hacer que este tipo de pruebas sea razonable de esperar. Hay punteros de propiedad exclusiva (es decir, sin alias), punteros a estructuras profundamente inmutables. El tipo de almacenamiento predeterminado es inmutable, etc.
También está la cuestión de aplicar una gramática real bien definida en los protocolos (que incluye miembros de la interfaz), para limitar el área de superficie de entrada exactamente a lo que se anticipa. La cuestión de la "corrección" es: 1) Deshacerse de todos los estados indefinidos 2) Garantizar la coherencia lógica . La dificultad de llegar allí tiene mucho que ver con el uso de herramientas extremadamente malas (desde el punto de vista de la corrección).
Esto es exactamente por qué las dos peores prácticas son variables globales y gotos. Estas cosas evitan poner condiciones pre / post / invariantes alrededor de cualquier cosa. También es por qué los tipos son tan efectivos. A medida que los tipos se fortalecen (en última instancia, usan Tipos dependientes para tener en cuenta el valor real), se acercan a ser pruebas de corrección constructivas en sí mismos; hacer que los programas inconsistentes fallen la compilación.
Tenga en cuenta que no se trata solo de errores tontos. También se trata de defender la base del código de los infiltrados inteligentes. Habrá casos en los que tendrá que rechazar un envío sin una prueba convincente generada por la máquina de propiedades importantes como "sigue el protocolo formalmente especificado".