R Namespaces: How Packages Export Functions & Prevent Conflicts
A namespace is the mechanism that lets R packages have internal (private) functions and public (exported) functions. It prevents packages from clashing when they define functions with the same name, and it ensures packages always find the right version of a function.
Without namespaces, every function from every loaded package would compete in one flat namespace. Two packages defining filter() would be chaos. Namespaces solve this by giving each package its own isolated environment.
# :: accesses exported functions (safe, documented)cat("stats::median(1:10) =", stats::median(1:10), "\n")cat("base::sum(1:10) =", base::sum(1:10), "\n")# ::: accesses internal functions (use with caution)# These are not part of the public API and may changecat("\nInternal function example:\n")cat("stats:::Pillai exists:", exists("Pillai", envir =asNamespace("stats")), "\n")
When to Use ::
RDisambiguate with :: for clarity
# Best practice: use :: for clarity and avoiding conflicts# Instead of loading the whole package:# library(stats)# result <- filter(data, ...) # Which filter? stats or dplyr?# Use :: to be explicit:x <-1:20smoothed <- stats::filter(x, rep(1/5, 5))cat("Smoothed (middle values):", round(smoothed[3:8], 2), "\n")
Imports and Exports
A package's NAMESPACE file declares:
Exports: functions available to users via library() or ::
Imports: functions from other packages that this package uses internally
RList exports and imports of stats
# See what a package exportsexports <-getNamespaceExports("stats")cat("stats exports", length(exports), "functions\n")cat("Sample:", paste(sample(exports, 8), collapse =", "), "\n")# See what a package importsimports <-getNamespaceImports("stats")cat("\nstats imports from", length(imports), "packages\n")cat("Packages:", paste(names(imports), collapse =", "), "\n")
The import mechanism is why packages work reliably: when ggplot2 calls a function internally, it finds the imported version in its namespace, not whatever version happens to be loaded in the user's session.
Namespace Resolution in Action
RHow namespaces resolve internal calls
# Demonstrate: package functions find OTHER package functions# through the namespace, not through the search path# Even if you mask a base function, packages still work:cat("Before: base::sum exists\n")# This doesn't break packages that use sum internally:# sum <- function(...) "I'm a fake sum"# stats::var() internally calls sum(), it finds base::sum# through its namespace imports, not through the search pathcat("stats::var(1:10) =", stats::var(1:10), "\n")cat("Namespace ensures packages find the right functions\n")
Practice Exercise
RExercise: count base exports and internals
# Exercise: For the "base" package, find:# 1. How many functions are exported# 2. How many total objects are in the namespace# 3. Name 3 functions that are in the namespace but NOT exported# Write your code below:
Explanation:getNamespaceExports() lists what's public. ls(asNamespace()) lists everything. The difference gives you internal-only objects that package authors use but don't expose to users.
Summary
Concept
Description
Access
Package env
Exported functions only
as.environment("package:stats")
Namespace env
All functions (public + private)
asNamespace("stats")
:: operator
Access exported function
stats::median()
::: operator
Access internal function
stats:::internal_fn()
Exports
What users can see
getNamespaceExports("pkg")
Imports
What the package uses internally
getNamespaceImports("pkg")
FAQ
Why shouldn't I use ::: in production code?
Functions accessed via ::: are not part of the package's public API. The package author can change, rename, or remove them at any time without warning. Your code could break silently after a package update.
How do I resolve conflicts when two packages export the same function name?
Three options: (1) Use package::function() to be explicit. (2) Load the package you want to use last, it masks the earlier one. (3) Use conflicted::conflict_prefer("filter", "dplyr") to declare your preference.