OOP did not invent encapsulation and is not synonymous with encapsulation. Many OOP languages do not have C++/Java style access modifiers. Many non-OOP languages have various techniques available to offer encapsulation.
One classic approach for encapsulation is closures, as used in functional programming. This is significantly older than OOP but is in a way equivalent. E.g. in JavaScript we might create an object like this:
function Adder(x) {
this.add = function add(y) {
return x + y;
}
}
var plus2 = new Adder(2);
plus2.add(7); //=> 9
The above plus2
object has no member that would allow direct access to x
– it's entirely encapsulated. The add()
method is a closure over the x
variable.
The C language supports some kinds of encapsulation through its header file mechanism, particularly the opaque pointer technique. In C, it is possible to declare a struct name without defining its members. At that point no variable of the type of that struct can be used, but we can use pointers to that struct freely (because the size of a struct pointer is known at compile time). For example, consider this header file:
#ifndef ADDER_H
#define ADDER_H
typedef struct AdderImpl *Adder;
Adder Adder_new(int x);
void Adder_free(Adder self);
int Adder_add(Adder self, int y);
#endif
We can now write code that uses this Adder interface, without having access to its fields, e.g.:
Adder plus2 = Adder_new(2);
if (!plus2) abort();
printf("%d\n", Adder_add(plus2, 7)); /* => 9 */
Adder_free(plus2);
And here would be the totally encapsulated implementation details:
#include "adder.h"
struct AdderImpl { int x; };
Adder Adder_new(int x) {
Adder self = malloc(sizeof *self);
if (!self) return NULL;
self->x = x;
return self;
}
void Adder_free(Adder self) {
free(self);
}
int Adder_add(Adder self, int y) {
return self->x + y;
}
There is also the class of modular programming languages, which focuses on module-level interfaces. The ML language family incl. OCaml includes an interesting approach to modules called functors. OOP overshadowed and largely subsumed modular programming, yet many purported advantages of OOP are more about modularity than object orientation.
There's also the observation that classes in OOP languages like C++ or Java are often not used for objects (in the sense of entities that resolve operations through late binding/dynamic dispatch) but merely for abstract data types (where we define a public interface that hides internal implementation details). The paper On Understanding Data Abstraction, Revisited (Cook, 2009) discusses this difference in more detail.
But yes, many languages have no encapsulation mechanism whatsoever. In these languages, structure members are left public. At most, there would be a naming convention discouraging use. E.g. I think Pascal had no useful encapsulation mechanism.